"""
     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 row<len(self.childFolders):
      return self.childFolders[row]
    else:
      row = row-len(self.childFolders)
      if row<len(self.childProjects):
        return self.childProjects[row]
    return None

  def columnCount(self):
    return 1

  def rowCount(self):
    return self.childCount()

  def childCount(self,mode='all'):
    if mode == 'folders':
      return len(self.childFolders)
    elif mode == 'projects':
      return  len(self.childProjects)
    else:
      return len(self.childProjects) + len(self.childFolders)

  def appendChildProject(self,child):
    self.childProjects.append(child)

  def appendChildFolder(self,child):
    self.childFolders.append(child)

  def removeChildProject(self,projectName):
    for ii in range(len(self.childProjects)):
      if self.childProjects[ii].refProject == projectName:
        del self.childProjects[ii]
        return CErrorReport()
   
    return CErrorReport(self.__class__,101,projectName)

  def removeChildFolder(self,folderName):
    for ii in range(len(self.childFolders)):
      if self.childFolders[ii].name == folderName:
        del self.childFolders[ii]
        return CErrorReport()
   
    return CErrorReport(self.__class__,101,folderName)

  def row(self):
    try:
      return self._parent.childFolders.index(self)
    except:
      print'CTreeItemFolder.row() failed'
      return -1

class CTreeItemProject(CTreeItem):
  # The project tree item currently only supports having child files
  # for use in the database browser
  def __init__(self,parent=None,infoList=[]):
    CTreeItem.__init__(self,parent=parent)
    self.projectId =  QtCore.QVariant(infoList[0])
    self.projectName = QtCore.QVariant(infoList[1])
    self.childProjects = []
    self.childFiles = []
    self.childJobs = []
    self.childFilesLoaded = False

  def isProject(self):
    return True

  def addDummyFile(self):
    # The files in a prject are loaded when user opens the project folder
    # but the project will only appear as a folder if we give it a dummy childfile
    self.appendChildFile(CTreeItemFile(self,[' ',' ',None,0,' ',' ','']))

  def clearFiles(self):
     self.childFiles = []

  def getProjectId(self):
    return self.projectId.toString().__str__()

  def getProjectName(self):
    return self.projectName.toString().__str__()

  def data(self,column,role):
    if role == QtCore.Qt.DisplayRole:
      if column == 0:
        return self.projectName
    elif role == QtCore.Qt.UserRole:
      if column == 0:
        return self.identifier

  def canFetchMore(self):
    return (not self.childFilesLoaded)
      
  def appendChildProject(self,child):
    self.childProjects.append(child)
    
  def appendChildFile(self,child):
    self.childFiles.append(child)
    
  def appendChildJob(self,child):
    self.childJobs.append(child)

  def child(self,row):
    if row<len(self.childProjects):
      return self.childProjects[row]
    else:
      row = row-len(self.childProjects)
      if row<len(self.childJobs):
        return self.childJobs[row]
      else:
        row = row-len(self.childJobs)
        if row<len(self.childFiles):
          return self.childFiles[row]     
    return None

  def columnCount(self):
    return 1

  def rowCount(self):
    return self.childCount()

  def childCount(self,mode='all'):
    if mode == 'files':
      return len(self.childFiles)
    elif mode == 'projects':
      return  len(self.childProjects)
    elif mode == 'jobs':
      return  len(self.childJobs)
    else:
      #print 'CTreeItemProject.childCount',len(self.childProjects) + len(self.childFiles)
      return len(self.childProjects) + len(self.childFiles) + len(self.childJobs)

  '''
  def childFileIndex(self,fileNode=None):
    if fileNode is not None and self.childFiles.count(fileNode):
      return self.childFiles.index(fileNode) 
    else:
      return -1
  '''

  def data(self,column,role):
    #print 'CTreeItemJob.data',column,role
    if role == QtCore.Qt.DisplayRole:
      if column == 0:
        return self.projectName
    elif role == QtCore.Qt.UserRole:
      if column == 0:
        return self.projectId
      '''
      elif role == QtCore.Qt.BackgroundRole:
        if self.highlight:
          return QtGui.QBrush(QtGui.QColor(CCP4StyleSheet.HIGHLIGHTCOLOUR))
        
        elif CProjectModel.CONTRAST_MODE == 1 and self.top:
          return QtGui.QBrush(QtGui.QColor(CCP4StyleSheet.LOWLIGHTCOLOUR))
                             
      elif role == QtCore.Qt.FontRole and CProjectModel.CONTRAST_MODE == 2:
        if self.top:
          return CTreeItemJob.boldFont
        else:
          return CTreeItemJob.italicFont
      '''
      
    return QtCore.QVariant()

  def row(self):
    try:
      return self._parent.childProjects.index(self)
    except:
      print'CTreeItemProject.row() failed'
      return -1
  
class CTreeItemFile(CTreeItem):
  def __init__(self,parent=None,infoList=[],displayColumn=2,maxChar=40,displayJobNumber=False):
    CTreeItem.__init__(self,parent=parent)
    #jid,fid,impid,ftype,fname,annotation = infoList
    self.displayColumn = displayColumn
    self.fileId = infoList[1]
    self.fileName = infoList[4]
    self.fileType = infoList[3]
    self.ifImport = infoList[2] is not None
    if len(infoList)>6:
      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 row<len(self.childInFiles):
      return self.childInFiles[row]
    else:
      row = row-len(self.childInFiles)
      if row<len(self.childOutFiles):
        return self.childOutFiles[row]
      else:
        row = row-len(self.childOutFiles)
        if row<len(self.childJobs):
          return self.childJobs[row]
        else:
          return None

  def childCount(self,mode='jobs'):
    if mode == 'infiles':
      return len(self.childInFiles)
    elif mode == 'outfiles':
      return  len(self.childInFiles) + len(self.childOutFiles)
    else:
      return len(self.childJobs) + len(self.childInFiles) + len(self.childOutFiles)

  def childFileIndex(self,fileNode=None):
    if fileNode is not None:
      if self.childInFiles.count(fileNode):
        return self.childInFiles.index(fileNode)
      elif self.childOutFiles.count(fileNode):
        return self.childOutFiles.index(fileNode) + len(self.childInFiles)
      else:
        return -1

  def data(self,column,role):
    if role == QtCore.Qt.DisplayRole:
      if column == 2:
        return self.name
      elif column == 3 and self.performance is not None:
        return self.performance.data(QtCore.Qt.DisplayRole)
      elif column == 4:
        return self.dateTime
    elif role == QtCore.Qt.UserRole:
      if column == 0:
        return self.identifier
    elif role == QtCore.Qt.DecorationRole:
      if column == 0:
        return self.followFrom
      elif column == 1:
        return self.evaluation
      elif column == 2:
        if self.top:
          return self.status
    elif role == QtCore.Qt.BackgroundRole:
      #print 'CJobTreeItem.data', self.statusStr
      if self.statusStr in  ['Running','Running remotely']:
         return QtGui.QBrush(QtGui.QColor(CCP4StyleSheet.RUNCOLOUR))
      if self.highlight:
        return QtGui.QBrush(QtGui.QColor(CCP4StyleSheet.LOWLIGHTCOLOUR))
      
      elif CProjectModel.CONTRAST_MODE == 1 and self.top:
        return QtGui.QBrush(QtGui.QColor(CCP4StyleSheet.LOWLIGHTCOLOUR))                          
                             
    elif role == QtCore.Qt.FontRole and CProjectModel.CONTRAST_MODE == 2:
      if self.top:
        return CTreeItemJob.boldFont
      else:
        return CTreeItemJob.italicFont
    elif role == QtCore.Qt.ToolTipRole:
      if column == 0:
        return QtCore.QVariant("Right mouse click to change 'follow from' status")
      elif column == 1:
        return QtCore.QVariant("Right mouse click to change 'opinion' status")
      else:
        return self.toolTip

        
    return QtCore.QVariant()

  def row(self):
    ii = -1
    for item in self._parent.childJobs:
      ii += 1
      if item == self: return ii
    return -1

  def getTopJob(self):
    # Track up to the top (pipeline) job
    #print 'CTreeItemJob.getTopJob',self,self.jobId
    if not isinstance(self._parent,CTreeItemJob):
      return self
    if self._parent.jobId is None:
      # its the root
      return self
    else:
      return self._parent.getTopJob()

  def getJobId(self):
    return self.jobId

  def getTaskName(self):
    return self.taskName

  def getStatus(self):
    if self.jobId is not None:
      return PROJECTSMANAGER().db().getJobInfo(jobId=self.jobId,mode='status')
    else:
      return None
      
  
  def isJob(self):
    return True

  def isTopJob(self):
    return isinstance(self._parent._parent,CProjectModel)

  def isFile(self):
    return False

  def mimeData(self):
    from lxml import etree
    urlList = []
    mimeType = 'FollowFromJob'
    root = etree.Element('jobId')
    root.text = str(self.jobId)
    dragText = etree.tostring(root,pretty_print=False)
    sceneFiles = PROJECTSMANAGER().getSceneFiles(jobId=self.jobId)
    if len(sceneFiles)>0:
      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 in ['Running','Running remotely']:
            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 jobStatus == 'Running remotely' or 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('Copy parameters')
          self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Copy parameters',jobId,itemName,modelIndex,None,selectedJobs))
          action.setEnabled(node.isTopJob())

          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 == 'Copy parameters':
      # Set 'taskParameters' data on the application clipboard
      # to enable it to be pasted elsewhere
      from lxml import etree
      root = etree.Element('taskParameters')
      jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId,mode=['taskname','jobnumber','projectname','projectid'])
      for name,value in [[ 'jobId' , jobId],['taskName',jobInfo['taskname']],['jobNumber',jobInfo['jobnumber']],['projectName',jobInfo['projectname']],['projectId',jobInfo['projectid']]]:
        e = etree.SubElement(root,name)
        e.text = value
      dragText = etree.tostring(root,pretty_print=True)
      data = QtCore.QByteArray()
      data.append(dragText)
      mimeData = QtCore.QMimeData()
      mimeData.setData('taskParameters_'+jobInfo['taskname'],data)
      QTAPPLICATION().clipboard().setMimeData(mimeData)
    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)