
"""
     CCP4ProjectViewer.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.sstac
 
     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 June 2011 - Project document in a QtMainWindow
"""

##@package CCP4ProjectViewer (QtGui)   Project document in a QtMainWindow
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtSvg import QSvgWidget

from CCP4ErrorHandling import *
from CCP4TaskManager import TASKMANAGER
from CCP4Modules import QTAPPLICATION,PROJECTSMANAGER,WEBBROWSER,LAUNCHER,MIMETYPESHANDLER, \
     PREFERENCES,JOBCONTROLLER,PIXMAPMANAGER,JOBCONTROLLERGUI
from CCP4Modules import WORKFLOWMANAGER,CUSTOMTASKMANAGER
from CCP4Config import DEVELOPER
import CCP4ProjectWidget
import CCP4WebBrowser
import CCP4DbApi
from CCP4MessageBox import CMessageBox
import functools,os
from CCP4DbUtils import COpenJob
import CCP4Utils
import CCP4GuiUtils
import CCP4TaskWidget

DEFAULT_WINDOW_SIZE = (1400,800)
ALWAYS_SHOW_SERVER_BUTTON = False

def isAlive(qobj):
  import sip
  try:
    sip.unwrapinstance(qobj)
  except RuntimeError:
    return False
  return True

def PROJECTVIEWER(projectId=None,open=False):
  for pv in CProjectViewer.Instances:
    if pv.taskFrame.openJob.projectId == projectId: return pv

  if open:
    pv = CProjectViewer(projectId=projectId)
    return pv

  return None

def getMenuIcon(parent,name,size):
  pixFile = TASKMANAGER().searchIconFile(name)
  fileName, fileExtension = os.path.splitext(pixFile)
  #print 'getMenuIcon',name,fileName, fileExtension 
  icon=None
  #icon = QtGui.QLabel(parent)
  #pix = CCP4GuiUtils.loadSvgWithSize(pixFile,size,size)
  #icon.setPixmap(pix)
  if fileExtension.lower() == ".svg":
    try:
      #icon = QSvgWidget(pixFile,parent=parent)
      #icon.setFixedSize(size,size)
      icon = QtGui.QLabel(parent)
      pix = CCP4GuiUtils.loadSvgWithSize(pixFile,size,size)
      icon.setPixmap(pix)
    except:
      print 'Unable to load SVG task icon'
      icon=None
  
  if icon is None:
    icon = QtGui.QLabel(parent)
    icon.setPixmap(QtGui.QPixmap(pixFile).scaled(size,size))

  return icon

def currentlyOpenJobs():
  jobIdList = []
  for pv in CProjectViewer.Instances:
    jobIdList.append(pv.taskFrame.openJob.jobId)
    for tw in pv.findChildren(CTaskMainWindow):
      jobIdList.append(tw.objectName()[3:])
  return jobIdList


#------------------------------------------------------------------------------------------------------
class CProjectViewer(CCP4WebBrowser.CMainWindow):
#------------------------------------------------------------------------------------------------------

  ERROR_CODES = { 101 : { 'description' : 'Error creating task widget for' },
                  102 : { 'description' : 'Wrong task name in imported params file' },
                  103 : { 'description' : 'Error attempting to auto-generate task widget for' },
                  104 : { 'description' : 'Error writing job parameters file' },
                  120 : { 'description' : 'Unknown error attempting to kill remote job' }
                  }

  INPUT_TAB = 0
  OUTPUT_TAB = 1
  COMMENT_TAB =2
  MARGIN=2

  Instances = []
  
  def __init__(self,parent=None,projectId=None,jobId=None,graphical=True):
    CCP4WebBrowser.CMainWindow.__init__(self,parent)
    CProjectViewer.Instances.append(self)
    self.setObjectName('projectViewer')
    self.connect(self,QtCore.SIGNAL("destroyed(QObject*)"),CProjectViewer.updateInstances)
    self._dictionaryWidget = None
  
    if jobId is None:
      jobIdList = PROJECTSMANAGER().db().getProjectJobList(projectId=projectId,topLevelOnly=True,maxLength=1)
      if len(jobIdList)>0: jobId = jobIdList[0]
    openJob = COpenJob(projectId=projectId,jobId=jobId)
    PROJECTSMANAGER().db().updateProject(projectId=projectId,key='lastaccess')
    #print 'CProjectViewer openJob', openJob 
    self.setWindowTitle(self.version+'Project Viewer: '+openJob.projectName)
    self.layout().setContentsMargins(CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN)
    self.layout().setSpacing(CProjectViewer.MARGIN)

    # left side project widget and buttons
    leftFrame = QtGui.QFrame(self)
    leftFrame.setLayout( QtGui.QVBoxLayout())
    leftFrame.layout().setContentsMargins(CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN)
    leftFrame.layout().setSpacing(CProjectViewer.MARGIN)

    self._projectWidget = CCP4ProjectWidget.CProjectWidget(self,projectId)
    self._projectWidget.setMinimumSize(QtCore.QSize(370,300))
    leftFrame.layout().addWidget(self._projectWidget)

    # Right side is task frame or task chooser
    self.rightStack = QtGui.QStackedWidget(self)
    self.taskFrame = CTaskFrame(self,projectId=openJob.projectId)
    self.taskFrame.buttons.frame.hide()
    self.rightStack.addWidget(self.taskFrame)
    self.taskChooser = CChooseTaskFrame(self)
    self.rightStack.addWidget(self.taskChooser)
    self.rightStack.setCurrentIndex(0)

    # left/right splitter
    self.splitterSizes = [400,CCP4TaskWidget.WIDTH+CCP4TaskWidget.PADDING_ALLOWANCE]
    mainWidget = QtGui.QSplitter(self)
    mainWidget.setOrientation(QtCore.Qt.Horizontal)
    mainWidget.addWidget(leftFrame)
    mainWidget.addWidget(self.rightStack)
    self.rightStack.layout().setContentsMargins(CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN)
    self.rightStack.layout().setSpacing(CProjectViewer.MARGIN)
    # And set the splitter sizes after show ...
    
    self.setCentralWidget(mainWidget)

    self.connect(self.taskFrame.outputFrame.webView,QtCore.SIGNAL('csvDownloadRequest'),self.handleCsvDownloadRequest)
    # Signals to open a new task
    self.connect(self.taskFrame.buttons.button('clone'),QtCore.SIGNAL('released()'),self.cloneTask)
    self.connect(self.taskFrame.buttons.button('help').menu(),QtCore.SIGNAL('aboutToShow()'),self.showHelpMenu)
    self.connect(self.taskFrame.buttons.button('help').menu(),QtCore.SIGNAL('triggered(QAction*)'),self.handleHelpMenu)
    self.connect(self.taskFrame.outputFrame,QtCore.SIGNAL('nextTask'),self.handleNextTask)
    self.connect(self.taskFrame.outputFrame,QtCore.SIGNAL('interrupt'),functools.partial(self.stopJob,False,False))
    self.connect(self.taskFrame,QtCore.SIGNAL('launchJobRequest'),self.launchJobRequest)
    #self.connect(self.taskFrame.buttons.button('next').menu(),QtCore.SIGNAL('triggered(QAction*)'),self.handleNextMenu)
    self.connect(self.taskChooser,QtCore.SIGNAL('taskClicked'),self.handleChooseTask)
    self.connect(self.taskChooser.taskTree,QtCore.SIGNAL('taskDropped'),self.handleChooseTaskAndFollowFrom)
    # Show chooser
    self.connect(self.taskChooser,QtCore.SIGNAL('closed'),functools.partial(self.showTaskChooser,False))
    # Handle job/project deleted
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobDeleted'),self.handleJobDeleted)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobStatusUpdated'),self.taskFrame.titleBar.setStatusBar)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobStatusUpdated'),self.updateActionEnabled)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.taskFrame.titleBar.setStatusBar)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('projectDeleted'),self.handleProjectDeleted)

    self.addToolBar(CCP4WebBrowser.CToolBar(self,'project','project'))
    toolbar = self.toolBar('project') 
    if toolbar is not None:
      toolbar.extend(['task_menu','view_coot','view_ccp4mg','export_mtz','task_help','references','clone','run'])
      #toolbar.extend(['task_menu','view_coot','view_ccp4mg','export_mtz','task_help','references','clone','job_search','run'])
      #if ALWAYS_SHOW_SERVER_BUTTON or JOBCONTROLLER().serversEnabled(): toolbar.extend(['run_remote'])
      toolbar.extend(['run_remote'])
      toolbar.setMovable(False)
      toolbar.show()

    #print 'CProjectViewer.init jobId',jobId
    if jobId is not None:
      #if DEVELOPER():
      #  openJob  = self.taskFrame.openTask(jobId=jobId)
      #else:
        try:
          openJob = self.taskFrame.openTask(jobId=jobId)
          self.setSelectedJob(jobId=openJob.jobId)
        except:
          print 'ERROR CProjectView.init opening jobId',jobId
    else:
      #Initialise task chooser open if not jobs in project
      self.showTaskChooser(True)


    if graphical: self.show()
    #print 'CProjectViewer.__init__ splitterSizes',self.splitterSizes
    mainWidget.setSizes(self.splitterSizes)
    self.connect(self.taskFrame.tab,QtCore.SIGNAL('currentChanged(int)'),self.handleTaskFrameChanged)

    #self.lastTaskFrameMode = CProjectViewer.OUTPUT_TAB
    #self.handleTaskFrameChanged(CProjectViewer.INPUT_TAB)
    self.shortcuts = {}
    self.setShortcuts()
    self.updateActionEnabled()
    self.toolBar('project').show()

    # Handle projectWidget actions
    self.connect(self._projectWidget,QtCore.SIGNAL('purge'),self.purgeJobFiles)
    self.connect(self._projectWidget,QtCore.SIGNAL('cloneTask'),self.cloneTask)
    self.connect(self._projectWidget,QtCore.SIGNAL('export'),self.exportJobFile)
    self.connect(self._projectWidget,QtCore.SIGNAL('deleteJob'),self.deleteJob)
    self.connect(self._projectWidget,QtCore.SIGNAL('interruptJob'),functools.partial(self.stopJob,False,False))
    self.connect(self._projectWidget,QtCore.SIGNAL('killJob'),functools.partial(self.stopJob,True,False))
    self.connect(self._projectWidget,QtCore.SIGNAL('killDeleteJob'),functools.partial(self.stopJob,True,True))
    self.connect(self._projectWidget,QtCore.SIGNAL('markFailedJob'),functools.partial(self.markFailedJob,False))
    self.connect(self._projectWidget,QtCore.SIGNAL('markFinishedJob'),functools.partial(self.markFailedJob,True))
    self.connect(self._projectWidget,QtCore.SIGNAL('currentJobChanged'),self.handleJobListSelectionChange)
    self.connect(self._projectWidget,QtCore.SIGNAL('nextJob'),self.handleNext)
    self.connect(self._projectWidget,QtCore.SIGNAL('openFrame'),self.openTaskMainWindow)
    self.connect(self._projectWidget,QtCore.SIGNAL('showWhatNextPage'),self.showWhatNextPage)
    self.connect(self._projectWidget,QtCore.SIGNAL('showBibliography'),self.showBibliography)
    self.connect(self._projectWidget,QtCore.SIGNAL('openMergeMtz'),functools.partial(self.launchJobRequest,'mergeMtz'))

    #print 'projectName',openJob.projectName
    pInfo = PROJECTSMANAGER().db().getProjectInfo(projectId,['I1ProjectName','I1ProjectDirectory'])
    if pInfo['i1projectname'] is not None:
      self.addI1Widget( pInfo['i1projectname'], pInfo['i1projectdirectory'])

    self.checkForRemotePasswords()

  def close(self):
    # Update the last access info for all open projects
    openProjectIdList = []
    for w in self.Instances:
      if w != self: openProjectIdList.append(w.taskFrame.openJob.projectId)
    #print 'CProjectViewer.close openProjectIdList',openProjectIdList
    PROJECTSMANAGER().db().updateProjectLastAcess(self.taskFrame.openJob.projectId,openProjectIdList)
    CCP4WebBrowser.CMainWindow.close(self)

  def projectId(self):
    return self.taskFrame.openJob.projectId
      
  def addI1Widget(self,projectName,projectDir):
    '''Open an i1 project viewer'''
    try:
      import CCP4I1Projects
      frame = QtGui.QFrame(self)
      frame.setLayout(QtGui.QVBoxLayout())
      frame.layout().setContentsMargins(0,0,0,0)
      frame.layout().setSpacing(0)
      self._projectWidget.tab.addTab(frame,'CCP4i project')
      self.i1Widget = CCP4I1Projects.CI1ProjectWidget(self)
      frame.layout().addWidget(self.i1Widget)
      #self._projectWidget.tab.addTab(self.i1Widget,'CCP4i project')
      infoList = [ projectName,projectName ]
      pObj = CCP4I1Projects.CI1TreeItemProject(self.i1Widget.model().rootItem,infoList=infoList,directory=projectDir)
      err=pObj.loadDatabase()
      if err.maxSeverity()>SEVERITY_WARNING:
        err.warningMessage('CCP4i project','Error loading old CCP4 project data',parent=self)
      else:
        self.i1Widget.model().beginResetModel()
        self.i1Widget.model().rootItem = pObj
        self.i1Widget.model().endResetModel()
    except exception as e:
      print e
      return

    try:
      line = QtGui.QHBoxLayout()
      line.setContentsMargins(3,3,3,3)
      line.setSpacing(2)
      frame.layout().addLayout(line)
      line.addWidget(QtGui.QLabel('CCP4i project: '+projectName))
      self.i1Reload = QtGui.QPushButton(self)
      self.i1Reload.setText('Reload')
      line.addWidget(self.i1Reload)
      self.connect(self.i1Reload,QtCore.SIGNAL('released()'),self.reloadI1Project)
    except:
      print e
    
    self.connect(self.i1Widget,QtCore.SIGNAL('showLogFile'),functools.partial(self.viewI1File,'log'))
    self.connect(self.i1Widget.projectView,QtCore.SIGNAL('fileClicked'),functools.partial(self.viewI1File,'clicked'))

    self.connect(self.i1Widget,QtCore.SIGNAL('viewInQtrview'),functools.partial(self.viewI1File,'qtrview'))
    self.connect(self.i1Widget,QtCore.SIGNAL('viewInCoot'),functools.partial(self.viewI1Job,'coot'))
    self.connect(self.i1Widget,QtCore.SIGNAL('viewInMg'),functools.partial(self.viewI1Job,'ccp4mg'))
    self.connect(self.i1Widget,QtCore.SIGNAL('viewDefFile'),functools.partial(self.viewI1Job,'def'))
    self.connect(self.i1Widget,QtCore.SIGNAL('reloadProject'),self.reloadI1Project)
    self.connect(self.i1Widget,QtCore.SIGNAL('viewFile'),functools.partial(self.viewI1File,'view'))
    self.connect(self.i1Widget,QtCore.SIGNAL('copyFile'),self.copyI1File)

  def viewI1Job(self,mode,projectId=None,jobId=None):
    '''View some file from a job from an i1 project'''
    #print 'viewI1Job',mode,projectId,jobId
    jItem = self.i1Widget.model().getJob1(jobId)
    if jItem is None:
        print 'ERROR in viewI1Job - could not find job:',jobId
        return
    if mode == 'def':
      defFile =os.path.join( self.i1Widget.model().rootItem.directory,'CCP4_DATABASE',str(jobId)+'_'+jItem.taskName+'.def')
      WEBBROWSER().openFile(defFile,format="text/plain")
    if mode in ['coot','ccp4mg']:
      fileList = []
      for fItem in jItem.childOutFiles:
        if fItem.broken>0:
          path = fItem.filePath()
          if os.path.splitext(path)[1] in ['.mtz','.pdb']:
            fileList.append(fItem.filePath())
      if mode == 'coot' : mode = 'coot0'
      LAUNCHER().openInViewer(viewer=mode,fileName=fileList)
    
  def viewI1File(self,mode,fileName,fileType=None):
    #print 'viewI1File',mode,fileName
    '''View a file from i1project viewer'''
    if fileName is None: return
    if mode == 'qtrview':
      if os.path.splitext(fileName)[1] == '.html':
        fileName = os.path.splitext(fileName)[0]
      LAUNCHER().launch('logview',[fileName])
    elif mode in ['view','clicked']:
      if fileType is None:
        ext = os.path.splitext(fileName)[1]
        if ext == '.mtz':
          fileType = "application/CCP4-mtz"
        elif ext == '.pdb':
          fileType = "chemical/x-pdb"
        else:
          fileType = "text/plain"
      if fileType == "application/CCP4-mtz":
        LAUNCHER().launch('hklview',[fileName])
      elif fileType == "chemical/x-pdb":
        WEBBROWSER().openFile(fileName)
      else:
        WEBBROWSER().openFile(fileName)

  def reloadI1Project(self):
    #print 'reloadI1Project'
    self.i1Widget.model().beginResetModel()
    err = self.i1Widget.model().rootItem.loadDatabase()
    if err.maxSeverity()>SEVERITY_WARNING:
        err.warningMessage('CCP4i project','Error loading old CCP4 project data',parent=self)
    self.i1Widget.model().endResetModel()
    
  def copyI1File(self,fileName):
    #print 'copyI1File',fileName
    if os.path.exists(fileName):
      urlList = [QtCore.QUrl()]
      urlList[0].setPath(fileName )
      mimeData = QtCore.QMimeData()
      mimeData.setUrls(urlList)
      QTAPPLICATION().clipboard().setMimeData(mimeData)
        
    
  def updateActionEnabled(self,jobStatus=None):
    if jobStatus is None: jobStatus = self.taskFrame.openJob.status
    #print 'CProjectViewer.updateActionEnabled taskFrame',self.taskFrame.buttons.mode,self.taskFrame.openJob.jobNumber,self.taskFrame.openJob.status
    
    #import traceback
    #traceback.print_stack(limit=5)

    if jobStatus is None:
      #No job set - disable all buttons
      #for item in ['run','view','clone']:
      for item in ['run','clone','task_help']:
        self.findChild(QtGui.QAction,item).setEnabled(False)
        for item in ['run_remote']:
          obj = self.findChild(QtGui.QAction,item)
          if obj is not None: obj.setEnabled(False)
    else:
      if self.taskFrame.buttons.mode in ['task','input']:
        self.findChild(QtGui.QAction,'run').setEnabled(jobStatus in ['Pending','Interrupted'])
        self.findChild(QtGui.QAction,'view_coot').setEnabled(jobStatus in ['Finished','Interrupted'])
        self.findChild(QtGui.QAction,'view_ccp4mg').setEnabled(jobStatus in ['Finished','Interrupted'])
        obj =  self.findChild(QtGui.QAction,'run_remote')
        if obj is not None: obj.setEnabled(jobStatus in ['Pending','Interrupted'])
      else:
        self.findChild(QtGui.QAction,'run').setEnabled(False)
        obj = self.findChild(QtGui.QAction,'run_remote')
        if obj is not None: obj.setEnabled(False)
      #self.findChild(QtGui.QAction,'view').setEnabled(jobStatus in ['Finished','Interrupted','To delete'])
      self.findChild(QtGui.QAction,'clone').setEnabled(True)

      refFileList = TASKMANAGER().searchReferenceFile(name=self.taskFrame.openJob.taskName,drillDown=True)
      if len(refFileList) == 0:
        # Is ther acustom biblio for this job
        import os,glob
        refFileList = glob.glob(os.path.join(self.taskFrame.openJob.jobDir,'*.medline.txt'))
      self.findChild(QtGui.QAction,'references').setEnabled((len(refFileList)>0))
      enabled = False
      exportMenu = []
      if jobStatus in ['Finished','Interrupted']:
        exportMenu = TASKMANAGER().exportJobFiles(taskName=self.taskFrame.openJob.taskName,jobId=self.taskFrame.openJob.jobId)
        for item in exportMenu:
          if item[0] == 'complete_mtz': enabled = True
        if not enabled:
          params = TASKMANAGER().getTaskAttribute(taskName=self.taskFrame.openJob.taskName,attribute='EXPORTMTZPARAMS',default=None)
          if params is not None: enabled = True
      #print 'updateActionEnabled',jobStatus,exportMenu,enabled
      self.findChild(QtGui.QAction,'export_mtz').setEnabled(enabled)   
      helpFile = TASKMANAGER().searchHelpFile(name=self.taskFrame.openJob.taskName)
      self.findChild(QtGui.QAction,'task_help').setEnabled((helpFile is not None))

  def runTask(self,mode='Now'):
    self.taskFrame.inputFrame.runTask(mode)


  def handleViewTask(self,mode):
    #print 'CProjectViewer.handleViewTask',mode
    self.taskFrame.handleViewTask(mode)

    
  def initialiseActionDefinitions(self):
    CCP4WebBrowser.CMainWindow.initialiseActionDefinitions(self)
    self.actionDefinitions['task_menu'] = dict (
          text = "Task menu",
          tip = "Show/hide the task menu",
          slot = functools.partial(self.showTaskChooser,True),
	      icon = 'taskmenu'
          )
    self.actionDefinitions['clone'] = dict (
          text = "Clone job",
          tip = "Make another job with same parameters",
          slot = self.cloneTask,
	      icon = 'clone'
          )
    self.actionDefinitions['run'] = dict (
          text = "Run",
          tip = "Run this task",
          slot = self.runTask,
	      icon = 'running'
          )
    self.actionDefinitions['run_remote'] = dict (
          text = "Run on server",
          tip = "Run this job on a server",
          slot = functools.partial(self.runTask,'run_remote'),
	      icon = 'running'
          )
    self.actionDefinitions['job_search'] = dict (
          text = "Job search",
          tip = "Search job list",
          slot = functools.partial(self.emit,QtCore.SIGNAL('jobSearch')),
	      icon = 'search'
          )
    
    self.actionDefinitions['view_coot'] = dict (
          text = "View in Coot",
          tip = "Show map & models related to the job in Coot",
          slot = functools.partial(self.handleViewTask,'view_coot'),
	      icon = 'coot_rebuild'
          )
    self.actionDefinitions['view_ccp4mg'] = dict (
          text = "View in CCP4mg",
          tip = "Show map & models related to the job in CCP4mg",
          slot = functools.partial(self.handleViewTask,'view_ccp4mg'),
	      icon = 'ccp4mg_edit_model'
          )
    self.actionDefinitions['export_mtz'] = dict (
          text = "Export MTZ",
          tip = "Export traditional MTZ file with all the data for the job",
          slot = self.exportMtz,
	      icon = 'MiniMtzDataFile'
          )
    self.actionDefinitions['task_help'] = dict (
          text = "Help",
          tip = "Show documentation for this task",
          slot = functools.partial(self.handleHelpMenu,'task_help'),
	      icon = 'help'
          )
    self.actionDefinitions['references'] = dict (
          text = "Bibliography",
          tip = "Show bibliographic references for the task",
          slot = functools.partial(self.handleHelpMenu,'references'),
	      icon = 'biblio'
          )
    

  def setShortcuts(self):
    for name,key in [ ['taskmenu' , QtCore.Qt.CTRL + QtCore.Qt.Key_M ],
                      ['nextproject' , QtCore.Qt.CTRL + QtCore.Qt.Key_N ]]:
      self.shortcuts[name] = QtGui.QShortcut(QtGui.QKeySequence(key),self)
      self.connect(self.shortcuts[name],QtCore.SIGNAL('activated()'),functools.partial(self.handleShortcut,name))

  def handleShortcut(self,mode):
    #print 'CProjectViewer.handleShortcut',mode
    if mode == 'taskmenu':
      self.showTaskChooser(True)
    elif mode == 'nextproject':
      idx = self.Instances.index(self) + 1
      if idx >= len(self.Instances): idx = 0
      #print 'CProjectViewer.handleShortcut nextproject',idx
      self.Instances[idx].show()
      self.Instances[idx].raise_()
      

  def projectWidget(self):
    return self._projectWidget

  def showHelpMenu(self):
    menu = self.taskFrame.buttons.button('help').menu()

    menu.clear()
    action = menu.addAction('Task description')
    action.setObjectName('task_help')
    action = menu.addAction('Bibliographic references')
    action.setObjectName('references')    
    
    refFileList = TASKMANAGER().searchReferenceFile(name=self.taskFrame.openJob.taskName,drillDown=True)
    menu.findChild(QtGui.QAction,'references').setEnabled((len(refFileList)>0))
    helpFile = TASKMANAGER().searchHelpFile(name=self.taskFrame.openJob.taskName)
    menu.findChild(QtGui.QAction,'task_help').setEnabled((helpFile is not None))

    # Add task-specific help
    programHelpList = TASKMANAGER().getTaskAttribute(self.taskFrame.openJob.taskName,'PROGRAMHELP',default=self.taskFrame.openJob.taskName)
    #print 'CTaskFrame.getHelpFile programHelp',programHelpList
    if programHelpList is not None:
      if not isinstance(programHelpList,list): programHelpList = [ programHelpList ]
      for programHelp in programHelpList:
        if programHelp.count('$CCP4I2'):
          helpPath = CCP4Utils.getCCP4I2Dir()+programHelp[7:]
        elif programHelp.count('$CCP4'):
          helpPath = CCP4Utils.getCCP4Dir()+programHelp[5:]
        else:
          helpPath = os.path.join(CCP4Utils.getCCP4Dir(),'html',programHelp+'.html')
        #print 'CTaskFrame.getHelpFile helpPath',helpPath
        if os.path.exists(helpPath):
          action = menu.addAction(os.path.splitext(os.path.split(helpPath)[-1])[0])
          action.setData(QtCore.QVariant(helpPath))


  def handleHelpMenu(self,mode):
    if not isinstance(mode,str):
      mode = str(mode.objectName())
    if mode == 'references':
      self.showBibliography(taskNameList=[self.taskFrame.openJob.taskName])
      
    elif mode == 'task_help':
      fileName = TASKMANAGER().searchHelpFile(name=self.taskFrame.openJob.taskName)
      WEBBROWSER().openFile(fileName)
    else:
      fileName = action.data().toString().__str__()
      WEBBROWSER().openFile(fileName)

  def reportAvailable(self):
    status = self.taskFrame.openJob.status in CCP4DbApi.FINISHED_JOB_STATUS or self.taskFrame.openJob.status in [ 'Running', 'Running remotely', 'Failed' ]
    #print 'reportAvailable',self.taskFrame.openJob.status,status
    return status

  def testReportAvailable(self):
    import glob
    testReportList = glob.glob(os.path.join(PROJECTSMANAGER().db().getProjectInfo(projectId=self.taskFrame.openJob.projectId,mode='projectdirectory'),'CCP4_test*.log'))
    return (len(testReportList)>0)
  
  def redoReport(self):
    try:
      self.taskFrame.outputFrame.showOutput(redo=True,doReload=True)
    except CException as e:
      e.warningMessage('Creating report','Error creating report',parent=self)

  def openTask(self,taskName=None,jobId=None,followJobId=None):
    try:
      openJob = self.taskFrame.openTask(jobId=jobId,taskName=taskName,followJobId=followJobId)
      self.setSelectedJob(jobId=openJob.jobId)
    except:
      print 'ERROR CProjectView.init opening jobId,taskName',jobId,taskName

  def openDictionary(self,state):
    #print 'CProjectViewer.openDictionary'
    if self._dictionaryWidget is None or (not isAlive(self._dictionaryWidget)):
      import CCP4ModelWidgets
      self._dictionaryWidget = CCP4ModelWidgets.CDictDataDialog(parent=self,projectId=self.getProject())
    self._dictionaryWidget.show()
    self._dictionaryWidget.raise_()

  def setSelectedJob(self,jobId):
    #print 'CProjectViewer.setSelectedJob',jobId
    self._projectWidget.selectJob(jobId)

  def Exit(self):
    PROJECTSMANAGER().db().updateProject(projectId=self.taskFrame.openJob.projectId,key='lastaccess')
    self.taskFrame.saveStatus()
  
  @staticmethod
  def updateInstances(qobj):
    l = []
    for w in CProjectViewer.Instances:
      if isAlive(w): l.append(w)   
    CProjectViewer.Instances = l
    #print 'projectviewer.updateInstances',CProjectViewer.Instances

  def getProject(self):
    #The current project and job data is held in a COpenJob member of CTaskFrame
    return self.taskFrame.openJob.projectId
      
  def getOpenJobNumber(self):
    '''
    Return the current job number
    '''
    return self.taskFrame.openJob.jobnumber 


  def showTaskChooser(self,state=None):
    '''Toggle display of task menu'''
    if state is None: state = (self.rightStack.currentIndex()==0)
    if state:
      self.rightStack.setCurrentIndex(1)
    else:
      self.rightStack.setCurrentIndex(0)
    
  def handleChooseTask(self,taskName):
    #print 'handleChooseTask',taskName
    self.handleChooseTaskAndFollowFrom(taskName)

  def handleChooseTaskAndFollowFrom(self,taskName,data=None):
    '''Open a selected task'''
    #print 'handleChooseTaskAndData',taskName
    self.showTaskChooser(False)
    openJob = self.taskFrame.openTask(taskName=taskName)
    self.setSelectedJob(jobId=openJob.jobId)
    #self._projectWidget.projectView.setJobSelection(jobId=self.taskFrame.openJob.jobId)

    #print 'handleChooseTaskAndFollowFrom',self.taskFrame.openJob
    
  def handleJobListSelectionChange(self,fileId,jobId,pipelineJobId,detatch):
    '''
    Keep the task display in step with the selection on the project job list
    '''
    #print 'handleJobListSelectionChange',fileId,jobId,pipelineJobId,detatch,'current',self.taskFrame.openJob.jobId
    #if pipelineJobId is None: return
    if detatch:
      jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['status','taskname'])
      if jobInfo['status'] in CCP4DbApi.FINISHED_JOB_STATUS:
        self.openTaskMainWindow('output',jobId)
      elif jobInfo['status'] in ['Running','Running remotely'] and TASKMANAGER().hasReportDefinition(name=jobInfo['taskname'],jobStatus=jobInfo['status']):
        self.openTaskMainWindow('output',jobId)
      else:
         self.openTaskMainWindow('input',pipelineJobId)
    else:
      if self.taskFrame.openJob.jobId is None or jobId != str(self.taskFrame.openJob.jobId):
        # Open task interface for the top level pipeline
        try:
          openJob = self.taskFrame.openTask(jobId=jobId)
        except CException as e:
          e.warningMessage('Opening new job','Failed opening job',parent=self)
        self.showTaskChooser(False)
        self.deleteSpawnedTaskWindows(jobId)
        #print 'handleJobListSelectionChange',openJob
      elif  jobId == str(self.taskFrame.openJob.jobId) and self.rightStack.currentIndex() == 1:
        self.showTaskChooser(False)

    
  def handleTaskFrameChanged(self,mode):
    '''If necessary create the task input frame when the task frame tab changed to input
      This is part of mechanism to ensure slow task frame drawing is only done when necessary'''
    #print 'handleTaskFrameChanged',mode,self.taskFrame.openJob.jobId,self.taskFrame.openJob.status
    if mode == CProjectViewer.INPUT_TAB and self.taskFrame.inputFrame.taskWidget is None:
      #print 'Need to draw taskWidget'
      import CCP4Container
      oJ = self.taskFrame.openJob
      defFile = TASKMANAGER().lookupDefFile(oJ.taskname,oJ.taskversion)
      container = CCP4Container.CContainer(parent=self.taskFrame,definitionFile=defFile,guiAdmin=True)
      paramsFile = PROJECTSMANAGER().makeFileName(jobId = oJ.jobId,mode='JOB_INPUT')
      container.loadDataFromXml(paramsFile)
      taskWidget = self.taskFrame.inputFrame.createTaskWidget(oJ.taskname,projectId=oJ.projectid,jobId=oJ.jobId,
                                               container=container,taskEditable=False)
      self.taskFrame.connect(taskWidget,QtCore.SIGNAL('launchJobRequest'),self.taskFrame.launchJobRequest)
      
    if mode == CProjectViewer.INPUT_TAB:
      self.splitterSizes = self.centralWidget().sizes()
      w = CCP4TaskWidget.WIDTH + CCP4TaskWidget.PADDING_ALLOWANCE
      if self.splitterSizes[1]<w:
        totWidth = self.splitterSizes[0]+self.splitterSizes[1]
        self.centralWidget().setSizes([totWidth-w,w])
        self.centralWidget().setStretchFactor(2,0)
    self.lastTaskFrameMode = mode

  def exportMtz(self):
    '''Export a monster MTZ of the job output'''
    self.exportJobFile( [ 'complete_mtz' ,'MTZ file' , 'application/CCP4-mtz' ] , str(self.taskFrame.openJob.jobId) )

  def exportJobFile(self,exportInfo,jobId):
    '''Export a job (by export project mechanism) or export specific output file'''
    #print 'CProjectViewer.exportJobFile',exportInfo,jobId,getattr(PROJECTSMANAGER(),'exportThread',None)
    if exportInfo == 'job':
      if getattr(PROJECTSMANAGER(),'exportThread',None) is not None:
        QtGui.QMessageBox.warning(self,'Export job','Please wait - another job currently being exported')
        return
      import CCP4FileBrowser
      self.browser = CCP4FileBrowser.CFileDialog(self,
           title='Save all job files',
           filters= [ MIMETYPESHANDLER().getMimeTypeInfo('application/CCP4-compressed-db','filter') ],
           defaultSuffix=MIMETYPESHANDLER().getMimeTypeInfo('application/CCP4-compressed-db','fileExtensions')[0],
           fileMode=QtGui.QFileDialog.AnyFile  )
      self.connect(self.browser,QtCore.SIGNAL('selectFile'),functools.partial(self.doExportJob,jobId))
      self.browser.show()
    else:
      taskName = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['taskname'])
      fromFile = TASKMANAGER().exportJobFiles(jobId=jobId,taskName=taskName,mode=exportInfo[0])
      #print 'CProjectViewer.exportJobFile',fromFile
      if fromFile is None or len(fromFile)==0 and exportInfo[0] =='complete_mtz':
        fromFile = PROJECTSMANAGER().exportJobMtzFile(jobId=jobId)
        if fromFile is None:
           QtGui.QMessageBox.warning(self,self.windowTitle(),'No export file for job')
           return
      if fromFile is not None:     
        import CCP4FileBrowser
        self.browser = CCP4FileBrowser.CFileDialog(self,
           title='Save '+exportInfo[1],
           filters= [ MIMETYPESHANDLER().getMimeTypeInfo(exportInfo[2],'filter') ],
           defaultSuffix=MIMETYPESHANDLER().getMimeTypeInfo(exportInfo[2],'fileExtensions')[0],
           fileMode=QtGui.QFileDialog.AnyFile  )
        self.connect(self.browser,QtCore.SIGNAL('selectFile'),functools.partial(self.doExportFile,jobId,fromFile))
        self.connect(self.browser,QtCore.SIGNAL('rejected()'),functools.partial(PROJECTSMANAGER().cleanupExportFiles,jobId))
        self.browser.show()

  def doExportFile(self,jobId,fromFile,toFile,move=False):
    '''Export a file by copying to selected path'''
    #print 'doExportFile',fromFile,toFile,move
    import shutil
    if move:
      shutil.move(fromFile,toFile)
    else:
      shutil.copyfile(fromFile,toFile)
    #PROJECTSMANAGER().cleanupExportFiles(jobId=jobId)
    
  def doExportJob(self,jobId,toFile):
    '''Export a job by project export'''
    projectId = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['projectid'])
    PROJECTSMANAGER().compressProject(projectId,jobList=[jobId],excludeI2files=False,fileName=toFile)

  def purgeJobFiles(self,jobId,context='temporary'):
    #print 'purgeJobFiles',jobId
    import CCP4ProjectsManager
    purger = CCP4ProjectsManager.CPurgeProject(jobId=jobId,projectId=self.taskFrame.openJob.projectId)
    purger.purgeJob(jobId=jobId,context=context)
    
  def cloneTask(self,oldJobId=None):
    '''Clone a job'''
    if oldJobId is None:
      try:
        if PROJECTSMANAGER().db().getJobInfo(jobId=self.taskFrame.openJob.jobId,mode='status') == 'Pending':
          self.taskFrame.inputFrame.makeJobInputFile()
      except:
        pass
      oldJobId = self.taskFrame.openJob.jobId
    clonedJobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=oldJobId,mode=['status','taskname'])
    #print 'cloneTask',oldJobId,taskname
    if oldJobId is None or clonedJobInfo['taskname'] is None: return
    if clonedJobInfo['status'] == 'Pending':
      #Do we have the Pending job open in a taskFrame - if so then save the params
      #import CCP4TaskWidget
      for taskWidget in self.findChildren(CCP4TaskWidget.CTaskWidget):
        if taskWidget._jobId == oldJobId: taskWidget.saveToXml()
    try:
      openJob =  self.taskFrame.openTask(taskName=clonedJobInfo['taskname'],cloneJobId=oldJobId)
    except CException as e:
      e.warningMessage('Opening new job','Failed opening '+str(clonedJobInfo['taskname'])+' job',parent=self)
      return
    #print 'cloneTask openJob',openJob,openJob.jobNumber
    #print 'cloneTask openJob',self.taskFrame.openJob,openJob.jobNumber 
    if openJob is not None:
      self.setSelectedJob(jobId=openJob.jobId)
      self.showTaskChooser(False)

  def stopJob(self,kill=False,delete=False,jobId=None):
    '''Stop a running job'''
    #print 'CProjectViewer.stopJob',kill,jobId
    taskName = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode='taskname')
    if kill and taskName == 'coot_rebuild':
      QtGui.QMessageBox.warning(self,self.windowTitle(),'Please close Coot from within Coot')
      return
    if kill:
      err = JOBCONTROLLER().killJobProcess(jobId=jobId)
      if delete:
         try:
           self.deleteJob(jobIdList=[jobId])
         except CException as e:
           err.extend(e)
           err.warningMessage('Deleting job','Failed deleting job',parent=self)
      else:
        import CCP4DbApi
        #print 'to updateJobStatus',jobId,type(jobId)
        PROJECTSMANAGER().db().updateJobStatus(jobId,CCP4DbApi.JOB_STATUS_FAILED)
    else:
      jobDir = PROJECTSMANAGER().jobDirectory(jobId=jobId,create=False)
      #print 'CProjectViewer.stopJob jobDir',jobDir
      if jobDir is not None:
        CCP4Utils.saveFile( os.path.join(jobDir,'INTERRUPT'),'Signal to interrupt job')


  def markFailedJob(self,succeeded=False,jobId=None):
    '''Set job status to finished or failed'''
    if succeeded:
      PROJECTSMANAGER().db().updateJobStatus(jobId=jobId,status = CCP4DbApi.JOB_STATUS_FINISHED)
    else:
      PROJECTSMANAGER().db().updateJobStatus(jobId=jobId,status = CCP4DbApi.JOB_STATUS_FAILED)
    
  def handleJobDeleted(self,args):
    '''Update gui on jobDeleted signal from database'''
    #print 'ProjectViewer.handleJobDeleted',argList,self.taskFrame.openJob.jobId
    if args['jobId'] == self.taskFrame.openJob.jobId:
      newJobId = self.suggestOpenJob()
      if newJobId is not None:
        openJob = self.taskFrame.openTask(jobId=self.suggestOpenJob())
        self.setSelectedJob(jobId=openJob.jobId)
      else:
        pass
    self.deleteSpawnedTaskWindows(args['jobId'])
    
  def deleteSpawnedTaskWindows(self,jobId):
    '''Delete the popout task windows'''
    children = self.findChildren(CTaskMainWindow,'job'+str(jobId))
    for child in children:
      child.close()
      child.deleteLater()

  def suggestOpenJob(self,lastOpenJobId=None):
    # Suggest an open job when the current one has been deleted
    # This only get called if the last job in list (usually the first job that was run) is deleted
    # because the Qt widget just moves the selection to the next job down list
    jobIdList = PROJECTSMANAGER().db().getProjectJobListInfo(projectId=self.taskFrame.openJob.projectId,mode='JobId',topLevelOnly=True,order='DESC')
    #print 'suggestOpenJob jobIdList',jobIdList
    if len(jobIdList)>0:
      return jobIdList[0]['jobid']
    else:
      return None

  def handleProjectDeleted(self,args):
    '''Close window if project has been deleted'''
    if args['projectId'] == self.taskFrame.openJob.projectId:
      self._projectWidget.setForceUpdate(False)
      self.close()
    
  def showHelp(self,mode='ccp4i2'):
    '''Show help pages'''
    WEBBROWSER().showHelp(mode=mode)

  def handleNext(self,nextTask,jobId,patchParamsFile):
    '''Handle user selection of next task (in Job list) by creating new job'''
    #print 'handleNext',nextTask,jobId,patchParamsFile
    openJob = self.taskFrame.openTask(taskName=nextTask,followJobId=jobId,patchParamsFile=patchParamsFile)
    self.setSelectedJob(jobId=openJob.jobId)
    PROJECTSMANAGER().db().updateJob(jobId=openJob.jobId,key='preceedingjobid',value=jobId)
  

  def handleNextTask(self,taskName,patchParamsFile):
    '''Handle Next task selection from buttons under report in CTaskFrame'''
    #print 'CProjectViewer.handleNextTask',taskName,patchParamsFile
    if taskName == 'clone_rerun':
      self.cloneTask()
    else:
      openJob = self.taskFrame.openTask(taskName=taskName,followJobId=self.taskFrame.openJob.jobId,patchParamsFile=patchParamsFile)
      self.setSelectedJob(jobId=openJob.jobId)

  def showWhatNextPage(self,jobId,taskName=None,projectId=None):
    '''Show a What next page'''
    nextPage = TASKMANAGER().getWhatNextPage(taskName=taskName,jobId=jobId)
    if nextPage is None:
      taskTitle= TASKMANAGER().getTitle(taskName)
      QtGui.QMessageBox.warning(self,self.windowTitle(),'Sorry - no What Next? page for '+str(taskTitle))
    else:
      view = WEBBROWSER().loadWebPage(helpFileName=nextPage)
      if view is not None:
        if projectId is None: projectId = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode='projectid')
        #print 'showWhatNextPage projectId',projectId
        view.report.setJobInfo(jobInfo= { 'jobId' : jobId, 'projectId' : projectId } )

  def showBibliography(self,jobId=None,taskNameList=[]):
    '''Show bibliography for task'''
    if jobId is None: jobId = self.taskFrame.openJob.jobId
    import CCP4BibliographyViewer
    viewer = CCP4BibliographyViewer.CBibliographyViewer(self)
    viewer.setReferences(jobId=jobId,taskNameList=taskNameList)
    viewer.show()

    
  def openApplication(self,application):
    '''Pass on request for program logs'''
    WEBBROWSER().openApplication(application)

  def launchJobRequest(self,taskName,args):
    #print 'CProjectViewer.launchJobRequest',taskName,args
    try:
      jobId,pName,jNumber = PROJECTSMANAGER().newJob(taskName=taskName,projectId=self.taskFrame.openJob.projectId)
    except:
      QtGui.QMessageBox.warning(self,'Error in creating new job','Unknown error creating new job'+str(taskName)+'\n'+str(e))
      return

    # Create an input params file for job
    import CCP4Container
    container = CCP4Container.CContainer(definitionFile=TASKMANAGER().lookupDefFile(taskName),guiAdmin=True)
    # Load known data to the params file
    if taskName == 'mergeMtz' and args.get('fileName',None) is not None:
      container.inputData.MINIMTZINLIST[0].set({'fileName':args.get('fileName',None)})
    container.saveDataToXml(fileName=PROJECTSMANAGER().makeFileName(jobId=jobId,mode='JOB_INPUT'))

    # If there is a jobId for a 'parent' job that launched this then it needs a call to CTaskWidget.handleLaunchedJobFinish
    # when the 'child' job finishes
    if args.get('launchJobId',None) is not None:
      # use non-existance of _launchedPopoutJobs attribute as flag to
      if getattr(self,'_launchedPopoutJobs',None) is None:
        self._launchedPopoutJobs = {jobId : args['launchJobId']}
        self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.handleJobFinished)       
    # Open a a popout window
    win = self.openTaskMainWindow('input',jobId)
    # Call 'parent' job to inform of progress and pass over reference to child widget
    #import CCP4TaskWidget
    childTaskWidget = None
    parentTaskWidget = None
    for taskWidget in self.findChildren(CCP4TaskWidget.CTaskWidget):
      if taskWidget.jobId() ==  jobId:
        childTaskWidget = taskWidget
      elif taskWidget.jobId() == args['launchJobId']:
        parentTaskWidget = taskWidget
    if parentTaskWidget is not None:
      parentTaskWidget.handleLaunchedJob(jobId=jobId,status=1,taskWidget=childTaskWidget)

  def handleJobFinished(self,args):
    '''Update a popout window when job status changed'''
    launchJobId = self._launchedPopoutJobs.get(args['jobId'],None)
    #import CCP4TaskWidget
    #print 'CProjectViewer.handleJobFinished taskWidgetList',self.findChildren(CCP4TaskWidget.CTaskWidget)
    if launchJobId is not None:
      for taskWidget in self.findChildren(CCP4TaskWidget.CTaskWidget):
        if taskWidget.jobId() == launchJobId:
          taskWidget.handleLaunchedJob(jobId=args['jobId'],status=args.get('status',None))
          del self._launchedPopoutJobs[args['jobId']]

  def openTaskMainWindow(self,mode,jobId=None):
    '''Open a popout window containing task input or report'''
    
    openJob = COpenJob(jobId=jobId)
    if mode == 'input':
      paramsFile = PROJECTSMANAGER().makeFileName(jobId = jobId,mode='JOB_INPUT')
      #print 'CProjectViewer.openTaskMainWindow paramsFile',paramsFile
      if not os.path.exists(paramsFile):
        #print 'CProjectViewer.openTaskMainWindow: Failed to find input params file',paramsFile
        return None
      defFile = TASKMANAGER().lookupDefFile(openJob.taskname,openJob.taskversion)
      if defFile is None:
        #print 'CProjectViewer.openTaskMainWindow: Failed to find def file',openJob.taskname
        return None
      import CCP4Container
      container = CCP4Container.CContainer(parent=self,definitionFile=defFile,guiAdmin=True)
      container.loadDataFromXml(paramsFile)
      #print 'CProjectViewer.openTaskMainWindow',repr(container),paramsFile
      taskEditable =  ( openJob.status in ['Unknown','Pending'] )
      try:
        widget = CTaskInputFrame(self)
        widget.createTaskWidget(openJob.taskname,projectId=openJob.projectid,jobId=jobId,container=container,taskEditable=taskEditable)
      except CException as e:
        e.warningMessage(parent=self)
        return None
      except Exception as e:
        QtGui.QMessageBox.warning(self,'Error in creating task input','Unknown error creating task input widget for job number '+str(openJob.jobnumber)+str(e))
    elif mode == 'output':
      #try:
      if 1:
        widget = CReportView(self)
        widget.showOutput(openJob=openJob)
      '''
      except CException as e:
        e.warningMessage(parent=self)
        return None
      except Exception as e:
        CMessageBox(self,message='Unknown error creating report file for job number '+str(openJob.jobnumber),exception=e,openJob=openJob)
        return
      '''
    elif mode == 'status':
      widget = CJobStatusWidget(self)
      widget.setJob(openJob)
      
    win = CTaskMainWindow(self,openJob.projectname,openJob.jobId)
    win.buttons.setMode(mode)
    import functools
    if win.buttons.button('clone') is not None:
      self.connect(win.buttons.button('clone'),QtCore.SIGNAL('released()'),functools.partial(self.cloneTask,jobId))
      self.connect(win.buttons.button('view').menu(),QtCore.SIGNAL('triggered(QAction*)'),functools.partial(self.handleViewTask0,jobId))
    if mode == 'input': self.connect(win.buttons.button('run'),QtCore.SIGNAL('released()'),win.runTask)
    win.titleBar.setOpenJob(openJob)
    #win.buttons.setNextMenu(jobId=jobId,taskName=openJob.taskname)
    win.buttons.setEnabled(openJob.status)
    win.centralWidget().layout().insertWidget(1,widget)
    win.show()
    win.raise_()
    return win
    #print 'CProjectViewer.handleOpenFrame widget',win.widget()
    
  def handleViewTask0(self,jobId,action=None):
    '''Show job files in ccp4mg or coot'''
    text = str(action.text())
    #print 'CProjectViewer.handleViewTask0',jobId,text
    if text.count('4mg'):
      LAUNCHER().openInViewer(viewer='ccp4mg',jobId=jobId,projectId=self.taskFrame.openJob.projectId,guiParent=self)
    elif text.count('oot'):
      LAUNCHER().openInViewer(viewer='coot_job',jobId=jobId,projectId=self.taskFrame.openJob.projectId,guiParent=self)
      
  def openSendReport(self):
    '''Open window to send developer error report'''
    import CCP4ErrorReportViewer
    widget = CCP4ErrorReportViewer.CSendJobError(self,projectId=self.taskFrame.openJob.projectId,projectName=self.taskFrame.openJob.projectName)
    widget.show()

  def openUpdate(self):
    '''Open the update manager'''
    import CCP4UpdateDialog
    widget = CCP4UpdateDialog.CUpdateDialog(self)
    widget.show()

  def deleteJob(self,jobIdList,deleteImportFiles=True):
    '''Delete one or more jobs'''
    followOnJobs = []
    allJobTree = []
    if not isinstance(jobIdList,list): jobIdList = [jobIdList]

    try:
      jobTreeList = PROJECTSMANAGER().db().getMultiFollowOnJobs(jobIdList=jobIdList,traceImportFiles=deleteImportFiles)
    except CException as e:
      e.warningMessage(parent=self,windowTitle=self.windowTitle(),message='Error attempting to find jobs dependent on output files')
      return

    xtrJobIdList = []
    for  jobTree in jobTreeList:
      for followOnJob in jobTree[2]:
        if followOnJob[0] not in jobIdList:
          xtrJobIdList.append(followOnJob[0])

    #Are there open pending jobs that might use files?
    jobsToDeleteWithSelectedFiles = []
    #print 'deleteJob openJob.status',self.taskFrame.openJob.status
    if self.taskFrame.openJob.status == 'Pending':
      selectedFileDict = self.taskFrame.inputFrame.taskWidget.container.inputData.inputFilesFileIds()
      #print 'deleteJob openJob selectedFileDict',selectedFileDict
      if len(selectedFileDict)>0:
        jobsWithSelectedFiles = PROJECTSMANAGER().db().getJobSourceOfFiles(fileIdList = selectedFileDict.keys())
        #print 'jobsWithSelectedFiles',jobsWithSelectedFiles

        jobsToDeleteWithSelectedFilesSet = set(jobIdList+xtrJobIdList) & set(jobsWithSelectedFiles)
        for i in range(len(jobsToDeleteWithSelectedFilesSet)): jobsToDeleteWithSelectedFiles.append(jobsToDeleteWithSelectedFilesSet.pop())
        #print 'deleteJob jobsToDeleteWithSelectedFiles',jobsToDeleteWithSelectedFiles
     

    if len(xtrJobIdList) == 0 and len(jobsToDeleteWithSelectedFiles) == 0:
      # Delete in reverse order
      for iJob in range(len(jobTreeList)-1,-1,-1):
        delJobId,importFiles,followOnJobs = jobTreeList[iJob]
        try:
          PROJECTSMANAGER().deleteJob(jobId=delJobId,importFiles=importFiles,projectId=self.taskFrame.openJob.projectId,
                                    deleteImportFiles=deleteImportFiles)
        except CException as e:
          e.warningMessage(parent=self,windowTitle=self.windowTitle(),message='Error attempting to delete job')
          return
        except Exception as e:
          QtGui.QMessageBox.warning(self,self.windowTitle(),'Unrecognised error deleting job\n'+str(e))
          return
        self.handleJobsDeleted()
    else:
      #QtGui.QMessageBox.warning(self,self.windowTitle(),"Deleting multiple jobs with 'knock on' jobs temporarily disabled\n")
      #return
      #print 'CProjectsViewer.deleteJob jobTree',jobTree
      deleteJobGui = CDeleteJobGui(self,self.taskFrame.openJob.projectId,jobIdList=jobIdList,jobTreeList=jobTreeList,
                    jobsToDeleteWithSelectedFiles=jobsToDeleteWithSelectedFiles,deleteImportFiles=deleteImportFiles,ifXtrJobs=len(xtrJobIdList)>0)
      self.connect(deleteJobGui,QtCore.SIGNAL('jobsDeleted'),self.handleJobsDeleted)
      deleteJobGui.show()

  def handleJobsDeleted(self):
    '''When job deleted reset the CDataFile 'job' combo to remove deleted job'''
    if self.taskFrame.inputFrame.taskWidget is not None:
      self.taskFrame.inputFrame.taskWidget.resetJobCombos()
      self.taskFrame.inputFrame.taskWidget.validate()
      
  def openManageImportFiles(self):
    '''Open window showing imported files for this project'''
    import CCP4ImpExpWidgets
    widget = CCP4ImpExpWidgets.CManageImportFiles(self,projectId=self.taskFrame.openJob.projectId)
    widget.show()

  def handleCsvDownloadRequest(self,jobId,dataName):
    #print 'CProjectView.handleCsvDownloadRequest',jobId,dataName
    '''Export a comma-separated-variables file containing data from a report table'''
    if jobId is None: jobId = self.taskFrame.openJob.jobId
    fileName = os.path.join(PROJECTSMANAGER().makeFileName(jobId= jobId,mode='TABLES_DIR'),dataName+'.csv')
    if not os.path.exists(fileName): return

    import CCP4FileBrowser
    self.fileBrowser = CCP4FileBrowser.CFileDialog(self,
           title='Save csv table file for '+dataName,
           filters= ['csv table file (*.csv)'],
           defaultSuffix='csv',
           fileMode=QtGui.QFileDialog.AnyFile  )
    self.connect(self.fileBrowser,QtCore.SIGNAL('selectFile'),functools.partial(self.downloadCsvFile,jobId,dataName))
    self.fileBrowser.show()

  def downloadCsvFile(self,jobId,dataName,targetFile):
    #print 'CProjectView.downloadCsvFile',jobId,dataName,targetFile
    sourceFile = os.path.join(PROJECTSMANAGER().makeFileName(jobId=jobId,mode='TABLES_DIR'),dataName+'.csv')
    import shutil
    try:
      shutil.copyfile(sourceFile,targetFile)
    except:
      QtGui.QMessageBox.warning(self,self.windowTitle(),'Failed to copy csv table file to '+str(targetFile))


  def grabWidget(self):
    '''Developer tool to grab image of task input or report widget'''
    import glob
    if not hasattr(self,'grabStatus'):
      self.grabStatus =  { 'taskName' : None , 'saveDir' : None, 'mode' : None }

    if self.taskFrame.tab.currentIndex() == CTaskFrame.INPUT_TAB:     
      wList = self.findChildren(CCP4TaskWidget.CTaskWidget)
      #print 'grabWidget wList',wList
      if len(wList)==0:
        QtGui.QMessageBox.warning(self,'Grab widget','Failed to find child task widget')
        return
      taskWidget = wList[0]

    elif self.taskFrame.tab.currentIndex() == CTaskFrame.OUTPUT_TAB:
      wList = self.findChildren(CReportView)
      #print 'grabWidget wList',wList
      if len(wList)==0:
        QtGui.QMessageBox.warning(self,'Grab widget','Failed to find child report view')
        return
      reportWidget = wList[0]
      
    else:
      return

    taskName = self.taskFrame.openJob.taskName
    if self.grabStatus['taskName'] is None or taskName != self.grabStatus['taskName']:
      rv = QtGui.QFileDialog.getExistingDirectory(caption='Select directory for saving screen grabs for task '+str(taskName))
      if rv is None or len(rv)==0: return
      self.grabStatus['saveDir'] = str(rv)
      self.grabStatus['taskName'] = taskName
    if self.taskFrame.tab.currentIndex() == CTaskFrame.INPUT_TAB:
      self.grabStatus['mode'] = 'task'
    else:
      self.grabStatus['mode'] = 'report'

    fileList = glob.glob(os.path.join(self.grabStatus['saveDir'],self.grabStatus['taskName']+'_'+self.grabStatus['mode']+'*.png'))
    if len(fileList)==0:
      fileName = os.path.join(self.grabStatus['saveDir'],self.grabStatus['taskName']+'_'+self.grabStatus['mode']+'_1.png')
    else:
      fileList.sort()
      num = int(fileList[-1][-6:-4].strip('_'))
      fileName = os.path.join(self.grabStatus['saveDir'],self.grabStatus['taskName']+'_'+self.grabStatus['mode']+'_' + str(num+1) + '.png')
      
    pix0 = QtGui.QPixmap()
    if self.grabStatus['mode'] == 'task':
      pix1 = pix0.grabWidget(taskWidget.widget)
    else:
      pix1 = pix0.grabWidget(reportWidget)
    #print 'grabWidget size', pix1.width(),pix1.height()
    f = QtCore.QFile(fileName)
    f.open(QtCore.QIODevice.WriteOnly)
    pix1.save(f, "PNG")
    f.close()
    print 'Saved to ',fileName
    WEBBROWSER().openFile(fileName)

  def queryDeleteJob(self,jobId,jobInfo):
    #print 'into queryDeleteJob'
    self.queryDeleteWindow = QtGui.QDialog(self)
    self.queryDeleteWindow.setWindowTitle(self.windowTitle())
    self.queryDeleteWindow.setModal(True)
    self.queryDeleteWindow.setLayout(QtGui.QVBoxLayout())
    self.queryDeleteWindow.layout().addWidget(QtGui.QLabel('''By default interactive jobs (such as ''' + TASKMANAGER().getShortTitle(jobInfo['taskname'])+ \
    ''') with no output files are deleted.\nYou can change this in the Preferences window.\nJob number '''+str(jobInfo.get('jobnumber',' ')) + \
    '''will be deleted.''', self ) )
    self.queryDelete = QtGui.QCheckBox('Delete interactive jobs with no output files',self.queryDeleteWindow)
    self.queryDelete.setChecked(bool(PREFERENCES().DELETE_INTERACTIVE_JOBS))
    self.queryShow = QtGui.QCheckBox('Do not show this warning again',self.queryDeleteWindow)
    self.queryShow.setChecked(True)
    self.queryDeleteWindow.layout().addWidget(self.queryDelete)
    self.queryDeleteWindow.layout().addWidget(self.queryShow)
    butBox = QtGui.QDialogButtonBox(self.queryDeleteWindow)
    butBox.addButton(QtGui.QDialogButtonBox.Ok)
    self.queryDeleteWindow.layout().addWidget(butBox)
    self.connect(butBox.buttons()[0],QtCore.SIGNAL('clicked()'),functools.partial(self.handleQueryDeleteJob,jobId))
    self.queryDeleteWindow.show()
    self.queryDeleteWindow.raise_()

  def handleQueryDeleteJob(self,jobId):
    ifShow = not(self.queryShow.isChecked())
    ifDelete = self.queryDelete.isChecked()
    #print 'handleQueryDeleteJob',jobId,ifShow,ifDelete
    self.queryDeleteWindow.hide()
    self.queryDeleteWindow.deleteLater()
    PREFERENCES().DELETE_INTERACTIVE_JOBS.set(ifDelete)
    PREFERENCES().SHOW_DELETE_INTERACTIVE_JOBS.set(ifShow)
    PREFERENCES().save()
    if ifDelete:
      try:
        PROJECTSMANAGER().db().deleteJob(jobId=jobId,deleteChildren=True)
      except:
        print 'Projectviewer handleQueryDeleteJob failed to delete job'
    else:
      import CCP4DbApi
      PROJECTSMANAGER().db().updateJobStatus(jobId=jobId,status=CCP4DbApi.JOB_STATUS_FINISHED)

      # Function to return list of names of exportable MTZ(s)
      
        
  def checkForRemotePasswords(self):
    '''Request remote machine password in order to check job status
      This is usually only relevant when i2 restarted after closing down
      with remote jobs running'''
    # i2 does not save user password on remote machines - check if we need a password
    # in order to check the remote machine
    requirePass = JOBCONTROLLER().checkForRemotePasswords(self.taskFrame.openJob.projectId)
    #print 'checkForRemotePasswords',requirePass
    if len(requirePass)==0: return
    import CCP4JobControlGui
    
    self.passwordEntry = CCP4JobControlGui.CPasswordEntry(parent=self,
      label='Please enter password for '+requirePass[0]['username']+' on '+requirePass[0]['machine']+'\nTo enable recovering remote jobs')
    self.connect(self.passwordEntry,QtCore.SIGNAL('passwordEntered'),functools.partial(self.handlePasswordEntry,requirePass[0]['jobId']))
    self.passwordEntry.show()

  def handlePasswordEntry(self,jobId,password):
    #print 'handlePasswordEntry',jobId,password
    JOBCONTROLLER().patchRemotePasswords(jobId,password)
    
  def widgetIsSaveable(self):
    return False
  def widgetIsPrintable(self):
    return False
  def widgetIsSearchable(self):
    return False
  def widgetIsRunable(self):
    return False
  def handleSave(self):
    pass
  def handlePrint(self):
    pass
  def handleRun(self):
    pass
  def openFind(self):
    pass
  def isFindFrameOpen(self):
    return False
  def deleteTab(self):
    pass
  def historyBack(self):
    pass
  def historyForward(self):
    pass
  def reloadPage(self):
    pass


class CTaskButtonsLayout(QtGui.QHBoxLayout):
  def maximumSize(self):
    return QtCore.QSize(CCP4TaskWidget.WIDTH,50)

  def sizeHint(self):
    return self.maximumSize()

class CTaskButtons(QtGui.QButtonGroup):
  '''Buttons now only used in the popout task window'''
  #BUTTONS =  ['run','view','clone','next']
  #BUTTONTEXT =  ['Run','View','Clone','Next']
  BUTTONS =  ['task_menu','help','view','clone','run']
  BUTTONTEXT =  ['Task menu','Help','View','Clone job','Run']
  TOOLTIPS = ['Open another task from the task chooser','Open task and program documentation','View the output maps and model in molecular graphics',
              'Create another job with the same parameters set','Check the input data then run the job']
  MOREINFO = 'More info..'
  DEFAULTMODE = 0
  RUNONLYMODE = 1
  
   #buttons
  def __init__(self,parent,parentLayout=None,mode=DEFAULTMODE):
    QtGui.QButtonGroup.__init__(self,parent)
    self.mode = 'task'  # task/input/output/status
      
    self.frame = QtGui.QFrame(parent)
    self.frame.setObjectName('buttons')
    layout = CTaskButtonsLayout()
    self.frame.setLayout(layout)
    layout.setContentsMargins(CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN,CProjectViewer.MARGIN)
    layout.setSpacing(0)
    layout.addStretch(1)
    if mode == CTaskButtons.RUNONLYMODE:
      butRange = [4]
    else:
      butRange = range(len(CTaskButtons.BUTTONTEXT))
    for ii in butRange:
      self.addButton(QtGui.QPushButton(CTaskButtons.BUTTONTEXT[ii],parent),ii)
      but = self.button(CTaskButtons.BUTTONS[ii])
      but.setToolTip(CTaskButtons.TOOLTIPS[ii])
      but.setAutoDefault(False)
      layout.addWidget(but)
      layout.addStretch(1)
    #parentLayout.addLayout(layout)
    parentLayout.addWidget(self.frame)
    self.button('run').setDefault(True)
    if mode==CTaskButtons.DEFAULTMODE:
      helpMenu = QtGui.QMenu(parent)
      self.button('help').setMenu(helpMenu)
      viewMenu = QtGui.QMenu(parent)
      self.button('view').setMenu(viewMenu)
      #nextMenu = QtGui.QMenu(parent)
      #self.button('next').setMenu(nextMenu)
      action = viewMenu.addAction('In CCP4mg')
      action = viewMenu.addAction('In Coot')
    if ALWAYS_SHOW_SERVER_BUTTON:
      runMenu =  QtGui.QMenu(parent)
      self.button('run').setMenu(runMenu)
      action = runMenu.addAction('Now')
      action = runMenu.addAction('Remotely - test output')
      action = runMenu.addAction('Remotely - import result')
    
  def setMode(self,mode):
    self.mode = mode

  def button(self,name):
    ii = CTaskButtons.BUTTONS.index(name)
    return QtGui.QButtonGroup.button(self,ii)


  def setRunMode(self,editor=None,status='Pending'):
    if editor:
      self.button('run').setText('Save')
    else:
      if status == 'Interrupted':
        self.button('run').setText('Restart')
      else:
        self.button('run').setText('Run')    
    
  def setEnabled(self,jobStatus):
    #print 'CTaskButtons.setEnabled',self.mode,jobStatus
    if jobStatus is None:
      #No job set - disable all buttons
      #for item in ['run','view','clone','next']:
      for item in ['run','view','clone']:
        b = self.button(item)
        if b is not None: b.setEnabled(False)
    else:
      if self.mode in ['task','input']:
        self.button('run').setEnabled(jobStatus in ['Pending','Interrupted'])
        #self.button('run').setEnabled(jobStatus in ['Pending'])
      else:
        self.button('run').setEnabled(False)
      if self.button('view') is not None:
        self.button('view').setEnabled(jobStatus in ['Finished','Interrupted','To delete'])
        self.button('clone').setEnabled(True)

    '''
    helpFileList = self.parent().getHelpFile()
    if len(helpFileList) == 0:
      #self.button('help').menu().setVisible(False)
      self.button('help').setEnabled( False )
    else:
      self.button('help').setEnabled( True )
      self.button('help').menu().clear()
      for text,filePath in helpFileList:
        #print 'CTaskButtons.setEnabled',text,'*',filePath
        action = QtGui.QAction(text,self)
        action.setData(QtCore.QVariant(filePath))
        self.button('help').menu().addAction(action)
    '''
      
      
  def setNextMenu(self,jobId=None,taskName=None):
    #print 'CTaskFrame.setNextMenu'
    nextList = TASKMANAGER().whatNext(taskName,jobId)
    menu = self.button('next').menu()
    menu.clear()
    for item in nextList:
      action = menu.addAction(item[1])
      if item[2] is not None:          
        action.setData(QtCore.QVariant(item[0]+' '+item[2]))
      else:
        action.setData(QtCore.QVariant(item[0]))
    menu.addSeparator()
    action=menu.addAction(CTaskButtons.MOREINFO)
    action.setData(QtCore.QVariant(taskName))

  

class CJobStatusWidget(QtGui.QFrame):

  MARGIN = 1

  def __init__(self,parent=None):
    QtGui.QFrame.__init__(self,parent=None)
    # Save the parent viewer - the Qt widget is reparented to a StackedWidget when
    # it is put in a tab
    self.openJob = COpenJob()
    self.commentId = None
    self.setLayout(QtGui.QVBoxLayout())
    self.layout().setContentsMargins(CJobStatusWidget.MARGIN,CJobStatusWidget.MARGIN,CJobStatusWidget.MARGIN,CJobStatusWidget.MARGIN)
    self.layout().setSpacing(CJobStatusWidget.MARGIN)

    splitter = QtGui.QSplitter(self)
    splitter.setOrientation(QtCore.Qt.Vertical)
    splitter.setSizes([2,1])
    self.layout().addWidget(splitter)

    
    # Annotation frame
    frame =  QtGui.QFrame(self)
    layout = QtGui.QVBoxLayout()
    frame.setLayout(layout)
    line =  QtGui.QHBoxLayout()
    line.addWidget(QtGui.QLabel('Rate this job:',self))
    self.thunk = QtGui.QComboBox(self)
    self.thunk.setEditable(False)
    for item in CCP4DbApi.JOB_EVALUATION_TEXT:
      self.thunk.addItem(CCP4ProjectWidget.jobIcon(item),item)
    line.addWidget(self.thunk)
    line.addWidget(QtGui.QLabel('or change job title..',self))
    line.addStretch(1)
    layout.addLayout(line)
    self.title = QtGui.QLineEdit(self)
    self.title.setMaxLength(255)
    layout.addWidget(self.title)
    line =  QtGui.QHBoxLayout()
    line.addWidget(QtGui.QLabel('Add comment..',self))
    line.addStretch(1)
    layout.addLayout(line)
    self.annotation = CCommentTextEdit(self)
    layout.addWidget(self.annotation)
    line =  QtGui.QHBoxLayout()
    line.addWidget(QtGui.QLabel('Previous comments..',self))
    line.addStretch(1)
    layout.addLayout(line)
    self.history = QtGui.QTextEdit(self)
    self.history.setReadOnly(True)
    layout.addWidget(self.history)
    splitter.addWidget(frame) 

    self.connect(self.thunk,QtCore.SIGNAL('currentIndexChanged(int)'),self.saveThunk)
    #self.connect(self.annotation,QtCore.SIGNAL('textChanged()'),self.saveAnnotation)
    self.connect(self.title,QtCore.SIGNAL('editingFinished()'),self.saveTitle)
    self.connect(self.annotation,QtCore.SIGNAL('newLine'),self.saveAnnotation)

    
  def setJob(self,openJob):
    #print 'CJobStatusWidget.setJob',jobId
    self.thunk.blockSignals(True)
    self.title.blockSignals(True)
    self.history.blockSignals(True)
    self.annotation.blockSignals(True)
    #print 'CJobStatusWidget.setJob',jobId,jobInfo
    self.saveAnnotation()
    self.openJob= openJob
    self.commentId = None
    if self.openJob.jobId is None:
      self.thunk.setCurrentIndex(0)
      self.annotation.setPlainText('')
      self.title.setText('')
      self.history.setReadOnly(False)
      self.history.clear()
      self.history.setReadOnly(True)
    else:
      self.thunk.setCurrentIndex(CCP4DbApi.JOB_EVALUATION_TEXT.index(self.openJob.evaluation))
      if self.openJob.jobtitle is not None:
        self.title.setText(self.openJob.jobtitle)
      else:
        self.title.setText('')
      self.annotation.setPlainText('')
      self.history.setReadOnly(False)
      self.history.clear()
      db = PROJECTSMANAGER().db()
      commentList = db.getComments(jobId=self.openJob.jobId)
      text = '<html><body>'
      for cid,user,time,comment in commentList:
        if db.timeIsToday(time):
          self.commentId = cid
          self.annotation.setPlainText(comment)
        else:
          strTime = db.timeString(time)
          #print 'setting history',user,comment
          text = text + '<h5>'+user+' on '+strTime+'</h5>\n<p>'+comment+'</p>\n'
      text = text + '</body></html>'
      self.history.setHtml(text)
      self.history.setReadOnly(True)
      
    self.thunk.blockSignals(False)
    self.title.blockSignals(False)
    self.history.blockSignals(False)
    self.annotation.blockSignals(False)
    self.connect(self.openJob,QtCore.SIGNAL('jobUpdated'),self.handleJobUpdated)
    #print 'CJobStatusWidget.setJob DONE'
         
  def saveThunk(self,indx):
    #print 'saveThunk',indx
    if self.openJob.jobId is None:
      #print 'calling saveThunk jobId None'
      return
    try:
      PROJECTSMANAGER().db().updateJob(jobId=self.openJob.jobId,key='evaluation',value=indx)
    except:
      pass
    
  def saveTitle(self):
    if self.openJob.jobId is None: return
    try:
      text = str(self.title.text())
      PROJECTSMANAGER().db().updateJob(jobId=self.openJob.jobId,key='jobTitle',value=text)
    except:
      pass

  def saveAnnotation(self):
    if self.openJob.jobId is None: return
    try:
      text = str(self.annotation.toPlainText())
      if len(text) > 0:
        #print 'saveAnnotation',text,type(text)
        if self.commentId is None:
          self.commentId = PROJECTSMANAGER().db().createComment(jobId=self.openJob.jobId,comment=text)
        else:
          PROJECTSMANAGER().db().updateComment(commentId=self.commentId,comment=text)
    except:
      pass  
    #print 'saveAnnotation',self.commentId

  def handleJobUpdated(self,key,value):
    #print 'CJobStatusWidget.handleJobUpdated',key,value
    if key == 'evaluation':
      self.thunk.blockSignals(True)
      self.thunk.setCurrentIndex(CCP4DbApi.JOB_EVALUATION_TEXT.index(self.openJob.evaluation))
      self.thunk.blockSignals(False)

      
    
class CCommentTextEdit(QtGui.QTextEdit):
  def keyReleaseEvent(self,event):
    #print 'CCommentTextEdit.keyReleaseEvent'
    if event.key() == QtCore.Qt.Key_Return:
      self.emit(QtCore.SIGNAL('newLine'))
      event.accept()
    else:
      event.ignore()

class CChooseTaskFrame(QtGui.QFrame):

  def __init__(self,parent):
    QtGui.QFrame.__init__(self,parent)
    self.setLayout(QtGui.QVBoxLayout())

    self.taskTree =  CTaskTree(self)
    #self.taskTree.setHeaderLabel('New task')
    self.taskTree.setHeaderHidden(True)
    self.layout().addWidget(self.taskTree)
    self.loadTaskTree()

    buttonGroup = QtGui.QButtonGroup(self)
    buttonGroup.addButton(QtGui.QPushButton('New job',self),0)
    buttonGroup.addButton(QtGui.QPushButton('Cancel',self),1)
    layout0 = QtGui.QHBoxLayout()
    layout0.addStretch(1)
    for but in buttonGroup.buttons():
      layout0.addWidget(but)
      layout0.addStretch(1)
    self.layout().addLayout(layout0)

    self.connect(self.taskTree,QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem *, int)'),self.handleChooseTask)
    self.connect(buttonGroup.button(0),QtCore.SIGNAL('released()'),self.handleChooseTask0)
    self.connect(buttonGroup.button(1),QtCore.SIGNAL('released()'),functools.partial(self.window().showTaskChooser,False))
    self.connect(WORKFLOWMANAGER(),QtCore.SIGNAL('listChanged'),self.loadTaskTree)
    self.connect(CUSTOMTASKMANAGER(),QtCore.SIGNAL('listChanged'),self.loadTaskTree)
    self.connect(PREFERENCES(),QtCore.SIGNAL('preferencesSaved'),self.loadTaskTree)

  def loadTaskTree(self):
    import CCP4StyleSheet
    compact = PREFERENCES().COMPACT_TASK_MENU
    self.taskTree.clear()
    for module,title,taskList in TASKMANAGER().taskTree():
      moduleItem = QtGui.QTreeWidgetItem()
      moduleItem.setFlags(QtCore.Qt.ItemIsEnabled)
      widget = CTaskTreeModuleWidget(title,iconName=module)
      #print 'loadTaskTree module',module
      self.taskTree.addTopLevelItem(moduleItem)
      self.taskTree.setItemWidget(moduleItem,0,widget)
      for taskName in taskList:
        #item =  CTaskWidgetItem(moduleItem,taskName,self.taskTree)
        item = QtGui.QTreeWidgetItem()
        item.setData(0,101,QtCore.QVariant(taskName))
        #widget = QtGui.QLabel(TASKMANAGER().getTitle(taskName))
        widget = CTaskTreeItemWidget(taskName,compact=compact)
        
        moduleItem.addChild(item)
        self.taskTree.setItemWidget(item,0,widget)
    
    #self.taskTree.expandAll()

    
  def handleChooseTask(self,item,column=None):
    try:
      taskName = str(item.data(0,101).toString())
    except:
      return
    if len(taskName)==0: return
    #print 'handleChooseTask',taskName,type(taskName)
    self.emit(QtCore.SIGNAL('taskClicked'),taskName)
    
  def handleChooseTask0(self):
    self.handleChooseTask(item=self.taskTree.currentItem())

class CTaskTreeModuleWidget(QtGui.QFrame):
  def __init__(self,moduleName,iconName=None):
    QtGui.QFrame.__init__(self)
    self.setObjectName('taskTreeModule')
    self.setLayout(QtGui.QHBoxLayout())
    MARGIN = 2 #was 1
    self.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN)
    self.layout().setSpacing(MARGIN)
    icon = getMenuIcon(self,iconName,24) 
    icon.setObjectName('icon')
    self.layout().addWidget(icon)
    label = QtGui.QLabel(moduleName,self)
    label.setObjectName('title')
    self.layout().insertSpacing ( 1, 4 )
    self.layout().addWidget(label,1,QtCore.Qt.AlignVCenter)
    self.layout().addStretch(5)

class CTaskTreeItemWidget(QtGui.QFrame):
  def __init__(self,taskName,compact=False):
    MARGIN = 4 # was 2
    QtGui.QFrame.__init__(self)
    self.setObjectName('taskTreeItem')
    layout = QtGui.QHBoxLayout()
    layout.setSpacing(MARGIN)
    layout.setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN)
    self.setLayout(layout)
    
    rank = TASKMANAGER().getTaskAttribute(taskName,'RANK')

    import os
    qticonsDir = os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons')
    if compact:
      size = 24
    else:
      self.setFrameStyle(QtGui.QFrame.NoFrame) # was QFrame.StyledPanel
      size = 48

      if rank == 1 :
        self.setObjectName ( 'taskpipe')
      else :
        self.setObjectName ( 'tasktool')

    icon = getMenuIcon(self,taskName,size) 
    icon.setObjectName('icon')
    self.layout().addWidget ( icon )
    self.layout().insertSpacing ( 1, 6 ) # this is new, provides space between icon and text
    
    label = QtGui.QLabel(TASKMANAGER().getTitle(taskName),self)
    if rank == 2:
      label.setObjectName('title2')
    else:
      label.setObjectName('title')
    if compact:
      self.layout().addWidget(label)
    else:
      vBox = QtGui.QVBoxLayout()
      self.layout().addLayout(vBox)
      vBox.addWidget(label,0,QtCore.Qt.AlignBottom)
      desc = TASKMANAGER().getTaskAttribute(taskName,'DESCRIPTION')
      label = QtGui.QLabel(str(desc))
      if rank == 2:
        label.setObjectName('description2')
      else:
        label.setObjectName('description')
      vBox.addWidget(label,0,QtCore.Qt.AlignTop)
      self.layout().addStretch(5)
      #if rank == 2: self.layout().insertSpacing ( 0, 48 )
    
    
class CTaskTree(QtGui.QTreeWidget):

  def __init__(self,parent):
    QtGui.QTreeWidget.__init__(self,parent)
    self.setObjectName('taskTree')
    self.setDragEnabled(True)
    self.setDragDropMode(QtGui.QAbstractItemView.DropOnly)
    self.setAcceptDrops(True)
 
  def supportedDropActions(self):
    #print 'CTaskTree.supportedDropActions'
    return QtCore.Qt.CopyAction

  def dropMimeData(self,parent,index,data,action):
    #print 'CTaskTree.dropMimeData',parent,index,data,action
    return True

  def mimeTypes(self):
    typesList = QtCore.QStringList()
    for item in MIMETYPESHANDLER().mimeTypes.keys(): typesList.append(item)
    typesList.append('jobid')
    #print 'CTaskTree.mimeTypes',typesList
    return typesList

  def dragEnterEvent(self,event):
    #print 'dragEnterEvent', event.mimeData().hasFormat('FollowFromJob')
    if event.mimeData().hasFormat('FollowFromJob'):
      event.accept()
    else:
      event.ignore()

  def dragMoveEvent(self,event):
    dropItem = self.itemAt(event.pos().x(),event.pos().y())
    #print 'dragMoveEvent',event.mimeData().hasFormat('project')
    if event.mimeData().hasFormat('jobid'):
      event.setDropAction(QtCore.Qt.CopyAction)
      event.accept()
    else:
      event.ignore()

  def dropEvent(self,event):
    #print 'dropEvent',event.mimeData().hasFormat('FollowFromJob')
    targetItem = self.itemAt(event.pos().x(),event.pos().y())
    if event.mimeData().hasFormat('FollowFromJob'):
      #print 'dropEvent targetProject',targetItem,targetProjectId
      taskName = str(targetItem.data(0,101).toString())
      data = str(event.mimeData().data('FollowFromJob').data())
      #print 'CTaskTree.dropEvent',data
      self.emit(QtCore.SIGNAL('taskDropped'),taskName,data)
      event.setDropAction(QtCore.Qt.CopyAction)
      event.accept()
    else:
      event.ignore()


class CTaskWidgetItem(QtGui.QTreeWidgetItem):

  def __init__(self,parent,taskName):
    self.taskName = taskName
    self.title = TASKMANAGER().getTitle(taskName)
    QtGui.QTreeWidgetItem.__init__(self,parent)

  def flags(self):   
    return QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsDropEnabled

  def data(self,ic,role):
    if ic == 0:
      if role == QtCore.Qt.FontRole:
         italicFont = QtGui.QFont()
         italicFont.setItalic(True)
         return italicFont
      if role == QtCore.Qt.DisplayRole:
        return QtCore.QVariant(self.title)
      if role == QtCore.Qt.ToolTipRole:
        desc = TASKMANAGER().getTaskAttribute(self.taskName,'DESCRIPTION')
        #print 'CTaskWidgetItem.data',self.taskName,desc
        if desc is not None:
          return QtCore.QVariant(desc)
        else:
           return QtCore.QVariant(self.taskName)
      if role == 101:
        return QtCore.QVariant(self.taskName)

    return QtCore.QVariant()

def FILEWATCHER():
  if CFileSystemWatcher.insts is None: CFileSystemWatcher.insts = CFileSystemWatcher()
  return  CFileSystemWatcher.insts


class CFileSystemWatcher(QtCore.QFileSystemWatcher):

  # Sub-class so we keep track of jobId as well as path
  insts = None

  def __init__(self,parent=None):
    if parent is None: parent = QTAPPLICATION()
    QtCore.QFileSystemWatcher.__init__(self,parent)
    # Fix for fail on NFS (and perhaps elsewhere) to convert to using internal poller mechanism
    # https://bugreports.qt.io/browse/QTBUG-8351
    if PREFERENCES().FILESYSTEMWATCHERPOLLER:
      self.setObjectName(QtCore.QLatin1String("_qt_autotest_force_engine_poller"))
    self.jobPaths = {}
    self.connect(self,QtCore.SIGNAL('fileChanged(const QString &)'),self.handleFileChanged)
    self.connect(self,QtCore.SIGNAL('directoryChanged(const QString &)'),self.handleDirectoryChanged)
    self.fileSizes = {}
    self._diagnostic = False

    self.jobsByUpdateInterval = {}

  def handleFileChanged(self,path):
    # Small changes introduced to handle a not-uncommon case.  Specifically, if a watched file is overwritten by doing
    # a pseudo-atomic overwrite (write to temporary file, and then rename over the top of existing file), then a
    # sad things happens: the rename causes the original file to be unlinked andhence removed from the
    # observed path list.
    import sys, os
    if self._diagnostic: print 'CFileSystemWatcher.handleFileChanged',path
    path = str(path)
    for jobId,pathList in self.jobPaths.items():
      if path in pathList:
        # Further processing appropriate only if the path represents a valid (existing) file object
        if os.path.isfile(path):
          # Ignore change in files that makes them > 100 characters (arbitrary cutoff)smaller (since probably implies they
          # are in the course of being overwritten)
          newFileSize = os.stat(path).st_size
          if path not in self.fileSizes or (path in self.fileSizes and (self.fileSizes[path] < (newFileSize+100))):
            self.emit(QtCore.SIGNAL('jobFileChanged'),jobId,path)
          self.fileSizes[path] = newFileSize
    return

  def clear(self):
    self.removePaths(self.files())
    self.jobPaths = {} 

  def addJobPath(self,jobId,path,updateInterval=None):
    import os, sys
    # Beware the file we want to watch might not exist
    # so put a watch on the (hopefully existing) parent directory
    if self._diagnostic: print 'FILEWATCHER.addJobPath',jobId,path,updateInterval
    if updateInterval is not None:
      if not self.jobsByUpdateInterval.has_key(jobId):
        self.jobsByUpdateInterval[jobId] = { 'path' : path , 'size' : 0 }
      return
  
    if not self.jobPaths.has_key(jobId): self.jobPaths[jobId] = []
    if not path in self.jobPaths[jobId]: self.jobPaths[jobId].append(path)
    
    directoryOfPath, fileName = os.path.split(path)
    currentlyWatchedDirectories = [str(directory) for directory in self.directories()]
    if not directoryOfPath in currentlyWatchedDirectories and os.path.exists(directoryOfPath): self.addPath(directoryOfPath)
    
    if os.path.isfile(path): self.addPath(path)
    
    if self._diagnostic: print 'CFileSystemWatcher Currently watching files:',[str(file) for file in self.files()]
    if self._diagnostic: print 'CFileSystemWatcher Currently watching directories:',[str(directory) for directory in self.directories()]

  def handleDirectoryChanged(self, path):
    #Here in case of a directory event: go through the
    #files we wish to be watching and add them to watch list *iff* they exist and are
    #not currently being watched
    import os
    currentlyWatchedPaths = [str(file) for file in self.files()]
    for jobId, pathsOfJob in self.jobPaths.items():
        for pathOfJob in pathsOfJob:
            directoryOfPath, fileName = os.path.split(pathOfJob)
            if directoryOfPath == str(path) and pathOfJob not in currentlyWatchedPaths and os.path.isfile(pathOfJob):
                self.addPath(pathOfJob)

  def removeJob(self,jobId):
    if  self.jobPaths.has_key(jobId):
      self.removePaths(self.jobPaths[jobId])
      del self.jobPaths[jobId]
    if self._diagnostic: print 'FILEWATCHER.removeJob',jobId,self.jobsByUpdateInterval.has_key(jobId)
    if self.jobsByUpdateInterval.has_key(jobId):
      del self.jobsByUpdateInterval[jobId]

  def removePath(self,path):
    for jobId,pathList in self.jobPaths.items():
      if path in pathList:
        self.jobPaths[jobId].remove(path)
        QtCore.QFileSystemWatcher.removePath(self,path)
        return

  def listJobs(self):
    print self.jobPaths

  def triggerJobsByUpdateInterval(self):
    for jobId,value in self.jobsByUpdateInterval.items():
      if os.path.isfile(value['path']):
        newSize = os.stat(value['path']).st_size
        if self._diagnostic: print 'FILEWATCHER',jobId,value,newSize
        if newSize > value['size']:
          value['size'] = newSize
          self.emit(QtCore.SIGNAL('jobFileChanged'),jobId,value['path'])

    
class CReportView(QtGui.QStackedWidget):

  def __init__(self,parent=None):
    QtGui.QStackedWidget.__init__(self,parent=parent)
    import functools
    import CCP4WebView
    #self.setLayout(QtGui.QVBoxLayout())
    #self.layout().setSpacing(0)
    #self.layout().setContentsMargins(1,1,1,1)
    self.setObjectName('reportView')
    self.openJob = COpenJob()
    self.openChildJob = None
    self.generator = None
    self._reportFile = None
    self.webFrame = QtGui.QFrame(self)
    self.webFrame.setLayout(QtGui.QVBoxLayout())
    self.webFrame.layout().setSpacing(0)
    self.webFrame.layout().setContentsMargins(1,1,1,1)
    self.webFrame.setObjectName('reportFrame')

    self.linksStack = QtGui.QStackedWidget(self)
    self.linksFrame = []

    labelList = [ [], [['Results','results'],['Input data','inputData'],['Output data','outputData'],['Show details','Show details'],['Job details','jobDetails']],
                  [['Error output','errors'],['Terminal output','terminal'],['Diagnostic','diagnostic']]
                ]

    for ii in range(3):
      self.linksFrame.append(QtGui.QFrame(self))
      self.linksFrame[ii].setObjectName('reportLinkFrame'+str(ii))
      self.linksFrame[ii].setFrameShape(QtGui.QFrame.StyledPanel)
      self.linksFrame[ii].setMaximumHeight(25)
      self.linksFrame[ii].setLayout(QtGui.QHBoxLayout())
      self.linksFrame[ii].layout().setSpacing(0)
      self.linksFrame[ii].layout().setContentsMargins(1,1,1,1)
      for label,link in labelList[ii]:
        but = QtGui.QPushButton(label,self)
        but.setFlat(True)
        self.linksFrame[ii].layout().addWidget(but)
        self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.handleLinkButton,link))        
      self.linksStack.addWidget(self.linksFrame[ii])

    self.webFrame.layout().addWidget(self.linksStack)
    
    self.webView= CCP4WebView.CWebView(self,blockLoading=True)
    self.connect(self.webView,QtCore.SIGNAL('loadFinished(bool)'),self.handleLoadFinished)
    self.webFrame.layout().addWidget(self.webView)
    self.webFrame.layout().setStretch(1,10.0)
    self.addWidget(self.webFrame)
    
    self.nextFrame = QtGui.QFrame(self)
    self.nextFrame.setObjectName('highlight')
    self.nextFrame.setLayout(QtGui.QGridLayout())
    self.nextFrame.layout().setSpacing(0)
    self.nextFrame.layout().setContentsMargins(0,0,0,0)
    self.webFrame.layout().addWidget(self.nextFrame)
    
    self.textView = None
    self.connect(self.webView.page(),QtCore.SIGNAL("NavigationRequest"),self.handleNavigationRequest)
    self.connect(self.webView.page(),QtCore.SIGNAL("CustomMimeTypeRequested"),self.handleCustomMimeTypeRequested)
    self.connect(FILEWATCHER(),QtCore.SIGNAL("jobFileChanged"),self.handleFileChanged)
    self.connect(JOBCONTROLLER(),QtCore.SIGNAL('remoteJobUpdated'),self.handleFileChanged)
    self.connect(self.openJob,QtCore.SIGNAL('jobStatusUpdated'),self.handleJobStatusUpdated)
    self.connect(self.openJob,QtCore.SIGNAL('workflowJobStatusUpdated'),self.handleWorkflowJobStatusUpdated)

    self.generator = None

  def setReportLinks(self,labelList=[]):
    # Set the self.linksFrame[1] which is the option for finished reports
    import functools
    for widget in self.linksFrame[1].findChildren(QtGui.QPushButton):
      self.linksFrame[1].layout().removeWidget(widget)
      widget.deleteLater()

    for label,brief in labelList:
      if brief is not None:
        but = QtGui.QPushButton(brief,self)
        but.setToolTip(label)
      else:
        but = QtGui.QPushButton(label,self)
      but.setFlat(True)
      self.linksFrame[1].layout().addWidget(but)
      self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.handleLinkButton,label))

  def clear(self):
    self.webView.clear()

  def setLinkButtons(self):
    try:
      linksObj= self.webView.report.getDataObject(id='links')
    except:
      pass
    #else:
    #  print 'CReportView.setLinks',linksObj

  def setNextButtons(self,taskName=None,jobId=None,status=None,interruptLabel=None):
    #print 'CReportView.setNextButtons',taskName,jobId,status,interruptLabel
    buts = self.nextFrame.findChildren(QtGui.QPushButton)
    for but in buts:
      but.hide()
      but.deleteLater()
    if status is not None:
      if status == 'Finished' or status == 'Unsatisfactory':
        nextList = TASKMANAGER().whatNext(taskName,jobId)
        ii = 0
        for link,label,defFile in nextList:
          but = QtGui.QPushButton(label,self)
          self.nextFrame.layout().addWidget(but,ii/3,ii%3)
          self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.emit,QtCore.SIGNAL('nextTask'),link,defFile))
          ii = ii + 1
      elif status == 'Failed':
        but = QtGui.QPushButton('Clone job to rerun',self)
        self.nextFrame.layout().addWidget(but,0,0)
        self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.emit,QtCore.SIGNAL('nextTask'),'clone_rerun',None))
      elif status in [ 'Running','Running remotely'] and taskName is not None:
        interruptLabel = TASKMANAGER().getTaskAttribute(taskName=taskName,attribute='INTERRUPTLABEL',default=None,script=True)
        #print 'setNextButtons interruptLabel',interruptLabel
        if interruptLabel is not None:
          but = QtGui.QPushButton(interruptLabel,self)
          self.nextFrame.layout().addWidget(but,0,0)
          self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.handleInterruptButton,jobId))

  def handleInterruptButton(self,jobId):
    self.setNextButtons(jobId=jobId)
    self.emit(QtCore.SIGNAL('interrupt'),jobId)

  def handleLinkButton(self,link):
    #print 'handleLinkButton',link
    url = self.webView.url()
    url.setFragment(link)
    # This should not be necessary..
    #self.webView.setTarget(link)
    self.webView.load(url)
    
  def handleNavigationRequest(self,url):
    # Intercept navigation requests and open links in another window 
    #print 'CReportView.handleNavigationRequest',str(url.toLocalFile()),str(url.fragment())
    path = str(url.path())
    if str(url.toLocalFile()) == str(CCP4Utils.getCCP4I2Dir())+"/docs/":
      # probably an internal link so show in the report window
      newUrl = QtCore.QUrl.fromLocalFile(self._reportFile)
      newUrl.setFragment(url.fragment())
      self.webView.load(newUrl)
      return
    
    if len(path)>11 and path[-11:] == 'report.html':
      jobId = self.webView.report.getLinkId(path)
      if jobId is not None:
        openJob = COpenJob(jobId=jobId)
        self.connect(self.openJob,QtCore.SIGNAL('jobStatusUpdated'),self.handleJobStatusUpdated)
        self.connect(self.openJob,QtCore.SIGNAL('workflowJobStatusUpdated'),self.handleWorkflowJobStatusUpdated)
        if not os.path.exists(path):
          # Requesting report file that is not yet created
          #print 'CReportView.handleNavigationRequest jobId',self.webView.report.linkIds,jobId
          if self.generator is None:
            import CCP4ReportGenerator
            self.generator = CCP4ReportGenerator.CReportGenerator(jobId=openJob.jobId,jobStatus=openJob.status,jobNumber=openJob.jobNumber)
            self.connect(self.generator,QtCore.SIGNAL('FinishedPictures'),self.handleMgFinished)
          #if DEVELOPER():          
          try:
            reportFile, newPageOrNewData = self.generator.makeReportFile()
          except CException as e:
            if reportErr and e.maxSeverity()>SEVERITY_WARNING:
              e.warningMessage(windowTitle=self.parent().windowTitle(),message='Failed creating job report',parent=self)
          except Exception as e:
            if reportErr:
              QtGui.QMessageBox.warning(self,self.parent().windowTitle(),'Unknown error creating report file for job number '+str(openJob.jobNumber))
          
          #print 'CReportView.handleNavigationRequest reportFile',reportFile
          if os.path.exists(reportFile):
            err = self.generator.mergeIntoParent(parentFile=self._reportFile)
            if err.maxSeverity()<=SEVERITY_WARNING: self.webView.reload()
        return

    # See if the web browser can do something
    WEBBROWSER().loadPage(url=url,newTab=True)
    WEBBROWSER().raise_()

  def handleCustomMimeTypeRequested(self,url):
    #print 'CReportView.handleCustomMimeTypeRequest',url
    WEBBROWSER().CustomMimeTypeRequested(url)
      
  def showOutput(self,openJob=None,reportFile=None,reportErr=True,redo=False,doReload=False):
    import CCP4WebView,CCP4TextViewer,CCP4ReportGenerator
    #print 'showOutput',openJob,reportFile,redo
    #import traceback
    #traceback.print_stack(limit=8)
    # If not report file then try making it from an outputXmlFile
    # Otherwise use a log or diagnostic file
    if openJob is None: openJob = self.openJob
    linkList = None
    newPageOrNewData = "NEWPAGE"

    # Do we have a log file ot fall back to?
    logFile = PROJECTSMANAGER().makeFileName(jobId=openJob.jobId,mode='LOG')
    if not os.path.exists(logFile):
      logFile = logFile + '.html'
      if not os.path.exists(logFile): logFile = None
    #print 'showOutput logFile',logFile,'reportFile',reportFile
      
    if reportFile is None:
      hasReportDef = TASKMANAGER().hasReportDefinition(openJob.taskname, openJob.status)
      #print 'showOutput hasReportDefinition',openJob.taskname, openJob.status,hasReportDef
      if openJob.isWorkflow:
        childOpenJob = openJob.childOpenJob(-1)
        #print 'showOutput workflow child',childOpenJob
        if childOpenJob is not None: openJob = childOpenJob
      #print 'showOutput',openJob.status,CCP4DbApi.FINISHED_JOB_STATUS,openJob.status in CCP4DbApi.FINISHED_JOB_STATUS
      if openJob.status == 'Failed':
        self.generator = CCP4ReportGenerator.CReportGenerator(jobId=openJob.jobId,jobStatus=openJob.status,jobNumber=openJob.jobNumber)
        reportFile = self.generator.makeFailedReportFile(redo=redo)
      elif  (openJob.status in CCP4DbApi.FINISHED_JOB_STATUS or (openJob.status in ['Running','Running remotely'] ) ) and hasReportDef:
        if self.generator is None or openJob.jobId != self.generator.jobId:
          self.generator = CCP4ReportGenerator.CReportGenerator(jobId=openJob.jobId,jobStatus=openJob.status,jobNumber=openJob.jobNumber)
          self.connect(self.generator,QtCore.SIGNAL('FinishedPictures'),self.handleMgFinished)
        else:
          self.generator.setJobStatus(openJob.status)
        # Comment out to ensure errors are trapped
        if DEVELOPER():
          reportFile, newPageOrNewData = self.generator.makeReportFile(redo=redo,doReload=doReload,useGeneric=(logFile is None))
        else:
          try:
            #print 'showOutput makeReportFile'
            if openJob.status == 'Failed':
              reportFile = self.generator.makeFailedReportFile(redo=redo)
            else:
              reportFile, newPageOrNewData = self.generator.makeReportFile(redo=redo,doReload=doReload,useGeneric=(logFile is None))
          except CException as e:
            # Dont report lack for report definition file
            if reportErr and e.maxSeverity()>SEVERITY_WARNING and e.code != 3:
              e.warningMessage(windowTitle=self.parent().windowTitle(),message='Failed creating job report',parent=self)
            reportFile = None
          except Exception as e:
            if reportErr:
              QtGui.QMessageBox.warning(self,self.parent().windowTitle(),'Unknown error creating report file for job number '+str(openJob.jobNumber))
            reportFile = None

      if reportFile is None:
        reportFile = PROJECTSMANAGER().makeFileName(jobId=openJob.jobId,mode='REPORT')
        if not os.path.exists(reportFile):
          if logFile is not None:
            reportFile = logFile
          else:   
            reportFile = PROJECTSMANAGER().makeFileName(jobId=openJob.jobId,mode='DIAGNOSTIC')
            if not os.path.exists(reportFile):
              reportFile = None
    
    if reportFile is None:
      reportFile = os.path.join(CCP4Utils.getCCP4I2Dir(),'docs','report_files','blank.html')

    #print 'CProjectViewer.showOutput reportFile',openJob.jobId,openJob.jobnumber,openJob.taskname,reportFile
    reloadedData = False
    if newPageOrNewData == 'NEWDATA' and reportFile is not None:
        #Here if we believe there is an existing report in the webview and we simply want new data
        #to be loaded into it
        loadedUrl=self.webView.url()
        reportUrl =  QtCore.QUrl.fromLocalFile(reportFile)
        if reportUrl == loadedUrl:
            nElementsUpdated, validConversion = self.webView.reloadReportData()
            #print nElementsUpdated, validConversion
            if validConversion and nElementsUpdated>0: reloadedData = True
    
    if not reloadedData:
        if reportFile is None or os.path.splitext(reportFile)[1] in ['.html','.htm']:
          self.setCurrentIndex(0)
          if reportFile is None:
            self.webView.load( QtCore.QUrl())
            self.linksStack.setCurrentIndex(0)
          else:
            url =  QtCore.QUrl.fromLocalFile(reportFile)
            self.webView.load(url)
            if openJob.status == 'Failed':
              self.linksStack.setCurrentIndex(2)
            else:
              if linkList is not None:
                self.setReportLinks(linkList)
              self.linksStack.setCurrentIndex(1)

        else:
          # Xml file - treat as text
          self.linksStack.setCurrentIndex(0)
          if self.textView is None:
            self.textView = CCP4TextViewer.CTextViewer(self)
            self.addWidget(self.textView)
          self.setCurrentIndex(1)
          try:
            self.textView.open( reportFile)
            self.textView.setFont(style='fixed_width')
          except:
            pass
    
    #print 'CReportView.showOutput',reportFile
    self._reportFile = reportFile
    if openJob.jobId != self.openJob.jobId:
      FILEWATCHER().removeJob(self.openJob.jobId)
      self.addWatchFile(openJob)
      self.openJob = openJob
      self.connect(self.openJob,QtCore.SIGNAL('jobStatusUpdated'),self.handleJobStatusUpdated)
      self.connect(self.openJob,QtCore.SIGNAL('workflowJobStatusUpdated'),self.handleWorkflowJobStatusUpdated)
    self.emit(QtCore.SIGNAL('reportAvailable'), self.openJob.jobId, (reportFile is not None))
    return reportFile
    #print 'CReportView.showOutput DONE'

  def handleLoadFinished(self,ok):
    #print 'CReportView.handleLoadFinished',ok
    #print self.webView.report.topFolds
    if not ok: return
    self.setReportLinks(self.webView.report.topFolds)
    
  def handleMgFinished(self,jobId):
    #if jobId == self.openJob.jobId: self.webView.reload()
    if jobId == self.openJob.jobId: self.webView.attemptImageLoad()


  def handleFileChanged(self,jobId,outputXmlFile):
    #print 'CReportView.handleFileChanged',jobId,outputXmlFile,self.openJob.jobId,self.openJob.status
    if jobId == self.openJob.jobId:
      self.showOutput(redo=True)
    elif self.openJob.childjobs is not None and jobId in self.openJob.childjobs:
      self.showOutput(openJob= self.openChildJob,redo=True)
    

  def handleJobStatusUpdated(self,status):
    #print 'CReportView.handleJobStatusUpdated',status,'openJob',self.openJob.jobId,self.openJob.taskName
    if status in ['Running']:
      self.addWatchFile(self.openJob)
    elif status in ['Finished','Failed']:
      FILEWATCHER().removeJob(self.openJob.jobId)
      #self.showOutput(redo=True)
    #print 'CReportView.handleJobStatusChanged watched files',FILEWATCHER().listJobs()

  def handleWorkflowJobStatusUpdated(self,argList):
    #argList = jobId,childJobId,taskName,status
    #print 'handleWorkflowJobStatusUpdated',argList
    if not argList[0] == self.openJob.jobId: return
    # NEED TO REMOVE FILEWATCH on subjobs too!
    FILEWATCHER().removeJob(self.openJob.jobId)
    if len(self.openJob.childjobs)>1: FILEWATCHER().removeJob(self.openJob.childjobs[-2])
    self.openChildJob = COpenJob(jobId=argList[1])
    self.addWatchFile(self.openChildJob)
    try:
      self.showOutput(openJob=self.openChildJob,redo=True)
    except:
      pass

  def addWatchFile(self,openJob):
    if not openJob.status in ['Running','Running remotely']: return
    #print 'addWatchFile',openJob.taskName
    # If there is specification for a 'running' report then watch the task program output
    runningXrt = TASKMANAGER().searchXrtFile(openJob.taskName,jobStatus='Running')
    if runningXrt is None:
      runningXrt = TASKMANAGER().getReportClass(openJob.taskName,jobStatus='Running')
    #print 'runTask runningXrt',openJob.taskName,runningXrt
    if runningXrt is not None:
      updateInterval = TASKMANAGER().getReportAttribute(openJob.taskName,'UPDATE_INTERVAL')
      watchFile = TASKMANAGER().getReportAttribute(openJob.taskName,'WATCHED_FILE')
      if watchFile is None:
        outputXmlFile = PROJECTSMANAGER().makeFileName(jobId=openJob.jobId,mode='PROGRAMXML')
      else:
        outputXmlFile = os.path.normpath(os.path.join(PROJECTSMANAGER().makeFileName(jobId=openJob.jobId,mode='ROOT'),watchFile))
      #print 'addWatchFile',openJob.taskName,outputXmlFile
      FILEWATCHER().addJobPath(jobId=openJob.jobId,path=outputXmlFile,updateInterval=updateInterval)
      


class CTaskInputFrame(QtGui.QFrame):

  lastOpenFolder = {}
  ERROR_CODES = { 
                  101 : { 'description' : 'Error attempting to run internal plugin' }
                  }

  def __init__(self,parent):
    QtGui.QFrame.__init__(self,parent)
    self.setLayout(QtGui.QVBoxLayout())
    self.layout().setContentsMargins(0,CProjectViewer.MARGIN,0,CProjectViewer.MARGIN)
    self.layout().setSpacing(CProjectViewer.MARGIN)
    self.taskWidget = None
    self.connect(PREFERENCES().TASK_WINDOW_LAYOUT,QtCore.SIGNAL('dataChanged'),self.redraw)
    self.connect(JOBCONTROLLER(),QtCore.SIGNAL('serverJobFailed'),self.handleServerJobFail)
   
    
  def createTaskWidget(self,taskName,projectId=None,jobId=None,container=None,taskEditable=True,followJobId=None,excludeInputData=False):
    # Create task widget
    taskWidgetClass = TASKMANAGER().getTaskWidgetClass(taskName)
    #print 'CTaskInput.createTaskWidget',taskName,taskWidgetClass,'followJobId',followJobId,'container',container,'taskEditable',taskEditable
    #if not taskEditable:
    #  import traceback
    #  traceback.print_stack(limit=5)
    
    if taskWidgetClass is not None:
      if DEVELOPER():
      #if 1:
        taskWidget = taskWidgetClass(self)
      else:
        try:
          taskWidget = taskWidgetClass(self)
        except CException as e:
          e.warningMessage('Error opening task window: '+str(taskName),parent=self)
          raise e
        except Exception as e:
          mess = QtGui.QMessageBox.warning(self,'Error opening task window: '+str(taskName),str(e))
          raise CException(self.__class__,101,taskName)
      taskWidget.folderAttributes.setAttribute(attribute='editable',folderFunction='all',value=taskEditable)
      taskWidget.setContainer(container)
    else:
      # Try for an auto-generated gui?
      import CCP4ContainerView
      taskWidget = CCP4ContainerView.CContainerView(self,container=container)
      taskWidget.folderAttributes.setAttribute(attribute='editable',folderFunction='all',value=taskEditable)
      #  raise CException(self.__class__,103,taskName)    
    taskWidget.setProjectId(projectId)
    taskWidget.setJobId(jobId)
    taskWidget.setDefaultParameters()
    taskWidget.excludeInputData = excludeInputData
    e = taskWidget.draw()
    #print 'createTaskWidget taskWidget HKLIN',repr(taskWidget.getContainer()),taskWidget.getContainer().inputData.HKLIN
    if len(e)>0: print 'drawTaskWidget errors',e.report()
    if followJobId is not None: taskWidget.setFollowJobId(followJobId)
    taskWidget.setDefaultFiles()
    
    if self.taskWidget is not None:
      CTaskInputFrame.lastOpenFolder[self.taskWidget.jobId()] = self.taskWidget.visibleFolder()
    self.closeTaskWidget()
    # Stick new widget in window
    self.taskWidget = taskWidget
    #print 'createTaskWidget',taskWidget
    if CTaskInputFrame.lastOpenFolder.get(jobId,None) is not None:
      self.taskWidget.setVisibleFolder(CTaskInputFrame.lastOpenFolder[jobId])
    self.layout().insertWidget(1,self.taskWidget)
    self.taskWidget.show()
    invalidList = self.taskWidget.isValid()
    return self.taskWidget

  def closeTaskWidget(self):
    if self.taskWidget is not None:
      # Close any exisiting task widget
      # Probably should be saving a def file
      self.taskWidget.close()
      self.taskWidget.deleteLater()
      self.taskWidget = None

      
  def runTask(self,runMode):
    # Update control file path in db and set status to queue the job
    #print 'CTaskInputFrame.runTask',runMode
    if isinstance(runMode,QtGui.QAction): runMode = runMode.text().__str__()
    if self.taskWidget is None: return

    self.taskWidget.updateModelFromView()

    # The method to fix any issues in user input
    rv = self.taskWidget.fix()
    if len(rv)>0:
      rv.warningMessage(parent=self,windowTitle='Running task',message='Error in input data')
      return
    # Check validity
    invalidList = self.taskWidget.isValid()
    taskErrors = self.taskWidget.taskValidity()
    #print 'ProjectViewer.runtask invalidList',invalidList
    if len(invalidList)>0 or len(taskErrors)>0:
      text = ''
      if len(invalidList)>0:
        text = text + 'Can not run task - missing or invalid data\nInvalid items are highlighted on the interface\n'
        for item in invalidList:
          try:
            itemName = item.objectPath()
          except:
            itemName = 'Unknown'
          if isinstance(item,str):
            text = text + item + '\n'
          else:
            for xtra in ['label','toolTip']:
              label = item.qualifiers(xtra)
              if label is not None and label is not NotImplemented: itemName = itemName +': '+label
            text = text + itemName + '\n'
          print 'Invalid data value',itemName,item
      if len(taskErrors)>0:
        text = text = taskErrors.report(user=True,ifStack=False)
      if not hasattr(self,'messageBox'):
        self.messageBox = QtGui.QMessageBox(self)
        self.messageBox.setWindowTitle(str(self.taskWidget.title()))
        but = self.messageBox.addButton('Close',QtGui.QMessageBox.RejectRole)
        self.connect(but,QtCore.SIGNAL('clicked()'),self.messageBox.close)
        self.messageBox.setDefaultButton(but)
        self.messageDetailsButton = self.messageBox.addButton('Details',QtGui.QMessageBox.AcceptRole)
        self.connect(self.messageDetailsButton,QtCore.SIGNAL('clicked()'),functools.partial(self.showInvalidDetails,invalidList))
      else:
        self.messageDetailsButton.show()
      self.messageBox.setText(text)
      self.messageBox.exec_()
      return


    jobId = self.taskWidget.jobId()
    projectId=self.taskWidget.projectId()
    taskName = self.taskWidget.taskName()
    container = self.taskWidget.getContainer()

    # Check if the task is blocked from local running
    print 'blockLocal',taskName, TASKMANAGER().getTaskAttribute(taskName,'blockLocal')
    if runMode.count('run_remote')==0 and TASKMANAGER().getTaskAttribute(taskName,'blockLocal'):
      title = TASKMANAGER().getTitle(taskName)
      QtGui.QMessageBox.warning(self,"Running "+title,
        "The task "+title+"\nand other large jobs should not be run on this computer.\nPlease use 'Run on server' to run on a bigger computer." )
      return
  
    # Remove unset items from lists
    #print 'CTaskInputFrame.runTask calling removeUnsetListItems'
    container.removeUnsetListItems()
    
    ifImportFile,errors = PROJECTSMANAGER().importFiles(jobId=jobId,container=container)
    #print 'CTaskInputFrame.runTask',ifImportFile,errors
    rv = self.makeJobInputFile()
    #print 'CTaskInputFrame.runTask makeJobInputFile',rv
    if not rv:
      QtGui.QMessageBox.warning(self,str(self.taskWidget.title()),'Job failed writing input parameters file')
      return

    subJobWidgets = getattr( self.taskWidget,'subJobTaskWidgets',{})
    for jobName,taskWidget in subJobWidgets.items():
      taskWidget.saveToXml()
      

    preceedingjobId = self.taskWidget.getContainer().guiAdmin.followFrom
    #print 'CTaskInputFrame.runTask setting preceedingjobId',preceedingjobId,type(preceedingjobId)
    PROJECTSMANAGER().db().updateJob(jobId=jobId,key='preceedingjobid',value=preceedingjobId.pyType())

    #Record input files in database
    import CCP4DbApi
    PROJECTSMANAGER().db().gleanJobFiles(jobId=jobId,container=container,projectId=projectId,roleList=[CCP4DbApi.FILE_ROLE_IN])

    #print 'runTask isInternalPlugin',taskName,TASKMANAGER().isInternalPlugin(taskName)
    if self.taskWidget.isEditor():
      PROJECTSMANAGER().updateJobStatus(jobId=jobId,status=CCP4DbApi.JOB_STATUS_FINISHED)
    else:
      # Delete a pre-existing report - assume we are restarting job
      try:
        os.remove(PROJECTSMANAGER().makeFileName(jobId=jobId,mode='REPORT'))
      except:
        pass
      
      if runMode.count('run_remote')>0:
        self.runRemotely(jobId,projectId)
        return
      elif TASKMANAGER().isInternalPlugin(taskName):
        try:
          self.runInternalTask()
        except:
          err = CException(self.__class__,101,taskName)
          err.warningMessage('Running internaltask','Failed running task',parent=self)   
      else:
        PROJECTSMANAGER().updateJobStatus(jobId=jobId,status=CCP4DbApi.JOB_STATUS_QUEUED)

    self.redrawTaskWidget()

  def redrawTaskWidget(self,editable=False):
    # Redraw task gui as non-editable
    import CCP4TaskWidget
    container = self.taskWidget.container
    taskName = self.taskWidget.taskName()
    jobId = self.taskWidget.jobId()
    projectId=self.taskWidget.projectId()
    #print 'runTask redrawing task widget',jobId,taskName
    if isinstance(self.taskWidget,CCP4TaskWidget.CTaskWidget) and PREFERENCES().TASK_WINDOW_LAYOUT == 'FOLDER':
      scrollDisp = self.taskWidget.getScrollDisplacement()
      folderOpenStatus = self.taskWidget.widget.getFolderOpenStatus()
      #print 'runTask folderOpenStatus',folderOpenStatus
      taskWidget = self.createTaskWidget(taskName,container=container,taskEditable=editable,jobId=jobId,projectId=projectId)
      taskWidget.widget.setFolderOpenStatus(folderOpenStatus)
      taskWidget.setScrollDisplacement(scrollDisp)
    else:
      taskWidget = self.createTaskWidget(taskName,container=container,taskEditable=editable,jobId=jobId,projectId=projectId)


  def runInternalTask(self):
    jobId = self.taskWidget.jobId()
    projectId=self.taskWidget.projectId()
    taskName = self.taskWidget.taskName()
    cls = TASKMANAGER().getPluginScriptClass(taskName)
    if cls is not None:
      db = PROJECTSMANAGER().db()
      db.updateJobStatus(jobId=jobId,status='Running')
      jobInfo = db.getJobInfo(jobId=jobId,mode=['projectid','projectname','jobnumber'])
      workDirectory = os.path.join(db.getProjectDirectory(projectId=jobInfo['projectid']),'CCP4_JOBS','job_'+str(jobInfo['jobnumber']) )
      if not os.path.exists(workDirectory): os.mkdir(workDirectory)
      name = str(jobInfo['projectname'])+'_'+str(jobInfo['jobnumber'])
      pluginObj = cls(parent=self,name=name,jobId=jobId,projectId=projectId)
      pluginObj.container.header.projectId = projectId
      pluginObj.container.header.jobId = jobId

      comFile = PROJECTSMANAGER().makeFileName(jobId=jobId,mode='JOB_INPUT')
      pluginObj.container.loadDataFromXml(comFile)

      import CCP4PluginScript
      dbHandler = CCP4PluginScript.CDatabaseHandler(projectId= jobInfo['projectid'],jobNumber=jobInfo['jobnumber'], projectName=jobInfo['projectname'])
      dbHandler.openDb()
      pluginObj.setDbData(handler=dbHandler,projectId= jobInfo['projectid'],jobNumber=jobInfo['jobnumber'], projectName=jobInfo['projectname'],jobId=jobId)
    
      pluginObj.process()

      return True
    else:
      return False

  def showInvalidDetails(self,invalidList):
    #print 'showInvalidDetails',invalidList
    text = 'Can not run task - missing or invalid data\nInvalid items are highlighted on the interface\n'
    for modelObj in invalidList:
      text = text + str(modelObj)+'\n'
      if modelObj is not None:
        text = text + modelObj.validity(modelObj.get()).report(ifStack=False) + '\n'

    self.messageDetailsButton.hide()
    self.messageBox.setText(text)
    self.messageBox.show()
      
  def makeJobInputFile(self):
    if self.taskWidget is None: return False
    #print 'CTaskInputFrame.makeJobInputFile',self.taskWidget,self.taskWidget._jobId
    #Beware -- this is trying to save status of previous task widget which may have
    #been deleted 
    try:
      status =  PROJECTSMANAGER().db().getJobInfo(jobId=self.taskWidget._jobId,mode=['status'])
    except:
      #print 'Error retrieving job info from db for job (CTaskInputFrame.makeJobInputFile)'
      print 'makeJobInputFile NOT saving input_params.xml file - db query fail'
      return False
    if status not in ['Pending','Interrupted']:
      # Ensure do not overwrite a job that is already started
      print 'makeJobInputFile NOT saving input_params.xml file'
      return False
    self.taskWidget.saveToXml()
    return True

  def makeJobBall(self,jobId,projectId,mechanism='ssh_shared'):
    import time,os,functools,re
    jobNumber = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['jobnumber'])
    projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId)
    #dbxml = JOBCONTROLLER().getServerParam(jobId,'dbXml')

    jobDir = PROJECTSMANAGER().db().jobDirectory(jobId=jobId)
    if mechanism not in ['ssh_shared','test','qsub_local','qsub_shared']:
      dbxml = os.path.join( projectInfo['projectdirectory'],'CCP4_TMP','DATABASE'+str(int(time.time()))+'.db.xml')
    else:
      dbxml = os.path.join( jobDir, 'DATABASE.db.xml' )
      JOBCONTROLLER().setServerParam(jobId,'dbXml',dbxml)

    #print 'makeJobBall',mechanism,dbxml
   
    inputFilesList,inputFileIdList,fromJobList,errReport =  PROJECTSMANAGER().getJobInputFiles(projectDir=projectInfo['projectdirectory'],jobIdList=[jobId],jobNumberList=[jobNumber])
    print 'runRemotely inputFilesList',inputFilesList,'fromJobList',fromJobList
    print 'runRemotely  errReport',errReport.report()
    fromJobIdList = []
    fromJobNumberList = []
    for item in fromJobList:
      fromJobIdList.append(item['jobid'])
      fromJobNumberList.append(item['jobnumber'])

    jobNumberList,errReport = PROJECTSMANAGER().db().exportProjectXml(projectId,fileName=dbxml,jobList=[jobId],inputFileList=inputFileIdList,inputFileFromJobList=fromJobIdList)
    if errReport.maxSeverity()>SEVERITY_WARNING:
        errReport.warningMessage(title,'Error creating XML database file',parent=self)
        return False

    if mechanism in ['ssh_shared','qsub_local','qsub_shared']:
      self.runRemotely1(jobId,projectId)
    else:
      import CCP4Export
      tarball = os.path.join( projectInfo['projectdirectory'],'CCP4_TMP','job_'+jobNumber+'_setup.ccp4db.zip')
      if os.path.exists(tarball): os.remove(tarball)
      self.exportThread = CCP4Export.ExportProjectThread(self,projectDir=projectInfo['projectdirectory'],dbxml=dbxml,target=tarball,jobList=[jobNumber],inputFilesList=inputFilesList,directoriesList=[],extraJobList=fromJobNumberList)
      self.exportThread.jobsWithInputFiles = [jobNumber]
      self.connect(self.exportThread,QtCore.SIGNAL('finished()'),functools.partial(self.runRemotely1,jobId,projectId))
      print 'makeJobBall starting',jobId,projectId,tarball
      self.exportThread.start()

  def runRemotely(self,jobId,projectId,message=None):
    dialog = JOBCONTROLLERGUI()
    jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId,['jobnumber','taskname'])
    dialog.setWindowTitle('Run '+jobInfo['jobnumber']+ ' ' + TASKMANAGER().getTitle(jobInfo['taskname']))
    dialog.setInfo(message)
    rv = dialog.exec_()
    print 'runRemotely',rv,dialog.valid(),dialog.get('mechanism')
    if rv == QtGui.QDialog.Accepted and dialog.valid():
      JOBCONTROLLER().createServerParams(jobId,dialog.getParams())
      print 'project viewer runRemotely runningReport',self.taskWidget.taskName(),TASKMANAGER().getReportClass(self.taskWidget.taskName(),jobStatus='Running')
      JOBCONTROLLER().setServerParam(jobId,'runningReport', (TASKMANAGER().getReportClass(self.taskWidget.taskName(),jobStatus='Running') is not None) )
      self.makeJobBall(jobId,projectId,dialog.get('mechanism'))

  def runRemotely1(self,jobId,projectId):
    print 'runRemotely1',jobId,projectId
    PROJECTSMANAGER().updateJobStatus(jobId=jobId,status=CCP4DbApi.JOB_STATUS_QUEUED)
    self.redrawTaskWidget()

  def handleServerJobFail(self,jobId,projectId,exception):
    print 'handleServerJobFail',jobId,projectId,exception.report(user=True,ifStack=False)
    if projectId != self.taskWidget.projectId(): return
    if jobId == self.taskWidget.jobId():
      self.redrawTaskWidget(editable=True)
    message = 'Failed to start remote job\n'+exception.report(mode=2,user=True,ifStack=False)
    if exception[0]['code'] == 331:
      message = message + '\nPlease check that work directory exists on remote machine'
    self.runRemotely(jobId,self.taskWidget.projectId(),message)
    
    
  def clear(self):
    if self.taskWidget is None: return
    self.taskWidget.close()
    self.taskWidget.deleteLater()
    self.taskWidget = None
    
  def redraw(self):
    if self.taskWidget is None: return
    taskName = self.taskWidget.taskName()
    jobId = self.taskWidget.jobId()
    projectId = self.taskWidget.projectId()
    container= self.taskWidget.getContainer()
    taskEditable = self.taskWidget.folderAttributes.attribute('editable')
    #print 'CProjectViewer.redrawTaskWidget',jobId
    # ? followJobId copied in container ?
    taskWidget = self.createTaskWidget(taskName,projectId=projectId,jobId=jobId,container=container,taskEditable=taskEditable)

  def parentProjectViewer(self):
    p = self.parent()
    while not isinstance(p,CProjectViewer):
      p = p.parent()
    return p

class CTaskTitleBarLayout(QtGui.QHBoxLayout):

  def minimumSize(self):
    return QtCore.QSize(CCP4TaskWidget.WIDTH,25)

  def sizeHint(self):
    return self.minimumSize()   

class CTaskTitleBar(QtGui.QFrame):
  MARGIN = 2
  def __init__(self,parent):
    QtGui.QFrame.__init__(self,parent)
    self.jobId= None
    self.setLayout(CTaskTitleBarLayout())
    self.layout().setContentsMargins(0,0,0,0)
    self.layout().setSpacing(0)
    self.title = QtGui.QLabel(self)
    self.title.setObjectName('jobTitle')
    self.layout().addWidget(self.title)
    self.status =  QtGui.QLabel(self)
    self.status.setObjectName('jobStatus')
    self.layout().addWidget(self.status)
    self.layout().setStretchFactor(self.title,3.0)
    self.layout().setStretchFactor(self.status,3.0)
    
    
    movie = QtGui.QMovie(os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons','running_1.gif')))
    self.icon = QtGui.QLabel(self)
    movie.setScaledSize(QtCore.QSize(24,24))
    self.icon.setMovie(movie)
    movie.start()
    self.icon.setObjectName('jobStatus')
    self.layout().addWidget(self.icon)

  def setOpenJob(self,openJob):
    #print 'CTaskTitleBar.setOpenJob',openJob.title
    self.title.setText(openJob.title)
    self.jobId = openJob.jobId
    self.setStatusBar({'jobId' : openJob.jobId, 'status' : openJob.info['status'] } )
                
  def setStatusBar(self,info={}):
    #print 'CTaskTitleBar.setStatusBar',info
    if info.get('jobId') != self.jobId: return
    status = info.get('status',0)
    if isinstance(status,int): status = CCP4DbApi.JOB_STATUS_TEXT[status]
    self.status.setText('The job is '+status)
    if status in  ['Running','Running remotely']:
      self.icon.show()
    else:
      #print 'CTaskTitleBar.setStatusBar hide'
      self.icon.hide()

        
class CTaskFrame(QtGui.QFrame):

  INPUT_TAB = 0
  OUTPUT_TAB = 1
  COMMENT_TAB =2
  MARGIN = 0
  ERROR_CODES = { 100 : { 'description' : 'Unknown error drawing task widget' } }
  def __init__(self,parent,projectId=None):
    QtGui.QFrame.__init__(self,parent)
    self.setLayout(QtGui.QVBoxLayout())
    self.layout().setContentsMargins(CTaskFrame.MARGIN,CTaskFrame.MARGIN,CTaskFrame.MARGIN,CTaskFrame.MARGIN)
    self.layout().setSpacing(CTaskFrame.MARGIN)
    self.openJob = COpenJob(projectId=projectId)
    self.titleBar = CTaskTitleBar(self)
    self.layout().addWidget(self.titleBar)

    self.tab = QtGui.QTabWidget(self)
    self.inputFrame = CTaskInputFrame(self)
    self.tab.addTab(self.inputFrame,'Input')
    self.outputFrame = CReportView(self)
    self.tab.addTab(self.outputFrame,'Results')

    self.statusFrame = CJobStatusWidget(self)
    self.tab.addTab(self.statusFrame,'Comments')
    self.setTaskTab('input')
    self.layout().addWidget(self.tab)


    bottomLayout = QtGui.QHBoxLayout()
    bottomLayout.setContentsMargins(0,0,0,0)
    bottomLayout.setSpacing(0)
    self.layout().addLayout(bottomLayout)
    self.buttons = CTaskButtons(self,parentLayout=bottomLayout)
    bottomLayout.addStretch()    
    self.connect(self.buttons.button('run'),QtCore.SIGNAL('released()'),functools.partial(self.inputFrame.runTask,'Now'))
    self.connect(self.buttons.button('run'),QtCore.SIGNAL('released()'),functools.partial(self.buttons.button('run').setDefault,False))
    
    if ALWAYS_SHOW_SERVER_BUTTON:
      self.connect(self.buttons.button('run').menu(),QtCore.SIGNAL('triggered(QAction*)'),self.inputFrame.runTask)
    self.connect(self.buttons.button('view').menu(),QtCore.SIGNAL('triggered(QAction*)'),self.handleViewTask)
    self.connect(self.buttons.button('task_menu'),QtCore.SIGNAL('released()'),functools.partial(self.window().showTaskChooser,True))
    self.connect(self.outputFrame,QtCore.SIGNAL('reportAvailable'),self.handleReportAvailable)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.handleJobFinished)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobStarted'),self.handleJobStarted)


  def saveStatus(self):
    self.inputFrame.makeJobInputFile()
    import CCP4DbUtils
    #print 'saveStatus',self.openJob
    if self.openJob.jobId is not None:
      try:
        CCP4DbUtils.makeJobBackup(jobId=self.openJob.jobId,projectName=self.openJob.projectName)
      except Exception as e:
        #print 'Failed to backup job details for job:'+str(self.openJob.jobId)+str(e)
        pass
    
  def setTaskTab(self,mode=None):
    if mode is None: return
    mode = mode.lower()
    if mode == 'status':
      self.tab.setCurrentIndex(self.COMMENT_TAB)
    elif mode == 'input':
      self.tab.setCurrentIndex(self.INPUT_TAB)
    elif mode == 'output':
      self.tab.setCurrentIndex(self.OUTPUT_TAB)

  
  def handleJobStarted(self,args):
    #print 'CTaskFrame.handleJobStarted',args,'openJob',self.openJob.jobId
    if args.get('jobId','') != self.openJob.jobId and  args.get('parentJobId','') != self.openJob.jobId : return
    self.outputFrame.setNextButtons(args.get('taskName',None), args['jobId'],status='Running')
    
  def handleJobFinished(self,args):
    jobId = args.get('jobId','')
    status = args.get('status',None)
    #print 'CTaskFrame.handleJobFinished',jobId,status,'openJob',self.openJob.jobId,self.openJob.status,status == self.openJob.status
    if jobId == self.openJob.jobId:
      if isinstance(status,int): status = CCP4DbApi.JOB_STATUS_TEXT[status]
      if status != self.openJob.status:
        self.openJob.status = status
        self.buttons.setRunMode(status=self.openJob.status)
        self.buttons.setEnabled(self.openJob.status)
        self.window().updateActionEnabled()
        self.outputFrame.showOutput(self.openJob,redo=True)
        self.outputFrame.setNextButtons(self.openJob.taskname,self.openJob.jobId,status)
        self.outputFrame.setLinkButtons()
    elif args.get('parentJobId','') == self.openJob.jobId:
      # Have finished a sub-job that might have been interruptable so clear the 'next' buttons
      self.outputFrame.setNextButtons(jobId=jobId)
    

  def updateTaskFrame(self,openJob=None):
    #print 'CTaskFrame.update',openJob,self.openJob
    import CCP4TaskManager
    reportFile = None
    if openJob is not None: self.openJob = openJob
    self.statusFrame.setJob(self.openJob)
    if self.openJob.jobId is None:
      self.buttons.button('next').menu().clear()
      self.buttons.setEnabled(self.openJob.status)
      self.inputFrame.clear()
      self.outputFrame.clear()
    else:
      try:
        reportFile = self.outputFrame.showOutput(self.openJob,reportErr=False)
      except CException as e:
        print e
        e.warningMessage(self.window().windowTitle(),'Error creating job report',parent=self)
      except Exception as e:
        print e
        CMessageBox(self,message='Error creating report for job number '+str(self.openJob.jobnumber),exception=e,openJob=self.openJob)
      
      self.buttons.setEnabled(self.openJob.status)
      if self.openJob.status == 'Running':
        runningSubJob = PROJECTSMANAGER().db().getRunningSubJob(jobId=self.openJob.jobId)
        if runningSubJob is not None:
          self.outputFrame.setNextButtons(runningSubJob['taskName'],runningSubJob['jobId'],'Running')
        else:
          self.outputFrame.setNextButtons(self.openJob.taskname,self.openJob.jobId,self.openJob.status)
      else:
        self.outputFrame.setNextButtons(self.openJob.taskname,self.openJob.jobId,self.openJob.status)
      self.outputFrame.setLinkButtons()
      if self.inputFrame.taskWidget is not None:
        self.buttons.setRunMode(editor=self.inputFrame.taskWidget.isEditor(),status=self.openJob.status)

    if (self.openJob.status in CCP4DbApi.FINISHED_JOB_STATUS or self.openJob.status in ['Running','Failed'] ) and reportFile is not None:
      self.setTaskTab('output')
      self.tab.setTabEnabled(self.OUTPUT_TAB,True)
    else:
      self.setTaskTab('input')
      if reportFile is None: self.tab.setTabEnabled(self.OUTPUT_TAB,False)
    self.window().updateActionEnabled(self.openJob.status)


  def handleViewTask(self,mode):
    if not isinstance(mode,str):
      mode = str(mode.text())
    #print 'CTaskFrame.handleViewTask',mode,self.openJob.jobId,self.openJob.jobNumber
    if mode.count('4mg'):
      LAUNCHER().openInViewer(viewer='ccp4mg',jobId=self.openJob.jobId,projectId=self.openJob.projectId,guiParent=self)
    elif mode.count('oot'):
      LAUNCHER().openInViewer(viewer='coot_job',jobId=self.openJob.jobId,projectId=self.openJob.projectId,guiParent=self)
      
  def handleReportAvailable(self,jobId,status):
    if jobId != self.openJob.jobId and jobId not in self.openJob.childjobs: return
    if status:
      self.tab.setTabEnabled(self.OUTPUT_TAB,True)
      self.setTaskTab('output')
    else:      
      self.tab.setTabEnabled(self.OUTPUT_TAB,False)
    
    
  def openTask(self,taskName=None,jobId=None,cloneJobId=None,followJobId=None,patchParamsFile=None):
    import CCP4Container,CCP4File
    import time

    #print 'CTaskFrame.openTask',taskName,jobId,'followJobId',followJobId,'cloneJobId',cloneJobId
    # If there is jobid try to get the paramsFile and ensure consistent taskName
    # ??? Should we be concerned about version number ???
    time1 = time.time()

    # Check we have a clone params file before creating new job
    cloneParamsFile = None
    if cloneJobId is not None:
      cloneParamsFile = PROJECTSMANAGER().makeFileName(jobId = cloneJobId,mode='JOB_INPUT')
      if not os.path.exists(cloneParamsFile): cloneParamsFile = PROJECTSMANAGER().makeFileName(jobId = cloneJobId,mode='PARAMS')
      if not os.path.exists(cloneParamsFile):
        QtGui.QMessageBox.warning(self,self.windowTitle(),'No parameter file found for task to clone')
        return None
    #print 'openTask',cloneJobId,cloneParamsFile
    paramsFile = None
    # If we are opening a pre-existing job then just ensure can read the params.def.xml file
    # or create a new job in the database and with a job directory
    if jobId is not None:
      paramsFile = PROJECTSMANAGER().makeFileName(jobId = jobId,mode='JOB_INPUT')
      # We could be loading a sub-job without a input params file
      if not os.path.exists(paramsFile): paramsFile = PROJECTSMANAGER().makeFileName(jobId = jobId,mode='PARAMS')
      #print 'CProjectViewer.openTask paramsFile',paramsFile,os.path.exists(paramsFile)
      if os.path.exists(paramsFile):
        header = CCP4File.xmlFileHeader(paramsFile)
        if taskName is None:
          taskName = str(header.pluginName)
        elif taskName != str(header.pluginName):
          err = CException(self__class__,102,'Suggested: '+str(taskName)+' File: '+str(paramsFile)+' Contains: '+str(header.pluginName))
          err.warningMessage('Error loading params file','Error loading params file for job id: '+jobId,parent=self)
          return None
      else:
          paramsFile = None
      ifNewJob= False
    else:
      jobId,pName,jNumber = PROJECTSMANAGER().newJob(taskName=taskName,projectId=self.openJob.projectId)      
      ifNewJob= True

    # Create an COpenJob instance to hold the meta-data for this job
    openJob=COpenJob(jobId=jobId,projectId=self.openJob.projectId)
    taskEditable =  ( openJob.status in ['Unknown','Pending'] )
    # For a cloned job copy the params.def.xml
    if cloneJobId is not None:
      if paramsFile is None:
        paramsFile = PROJECTSMANAGER().makeFileName(jobId = openJob.jobId,mode='JOB_INPUT')
      try:
        import CCP4File
        CCP4File.cloneI2XmlFile(cloneParamsFile,paramsFile,{ 'jobId' : str(openJob.jobnumber) } )
        openJob.clonedFromJobId = cloneJobId
        #print 'CTaskFrame.openTask cloneFromJobId',cloneJobId, openJob.clonedFromJobId
      except:
        print 'ERROR cloning params file',cloneParamsFile
        paramsFile = None
      else:
        import glob
        splitCloneParamsFile = os.path.split(cloneParamsFile)      
        subJobParamsFiles = glob.glob(os.path.join(splitCloneParamsFile[0],'job_*_'+splitCloneParamsFile[1]))
        for subFile in subJobParamsFiles:
          #print 'CTaskFrame.openTask clone',subFile
          CCP4File.cloneI2XmlFile(subFile,os.path.join(os.path.split(paramsFile)[0],os.path.split(subFile)[1]),{ 'jobId' : str(openJob.jobnumber) } )

    # Find the task def file and create a CContainer with data contents based on the def file
    defFile = TASKMANAGER().lookupDefFile(openJob.taskname,openJob.taskversion)
    if defFile is None:
      print 'Failed to find def file in openTask'
      print 'CTaskFrame.openTask defFile',openJob.taskname,type(openJob.taskname),defFile,openJob.taskversion
      return self.openJob
    
    # Set up data container
    container = CCP4Container.CContainer(parent=self,definitionFile=defFile,guiAdmin=True)
    if patchParamsFile is not None:
      #print 'openTask patchParamsFile',patchParamsFile
      # expect patch params file to be passed thru from a whatNext command
      if patchParamsFile.startswith('$CCP4I2'):
          patchParamsFile = os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),patchParamsFile[8:]))
      elif patchParamsFile.startswith('$PROJECT'):
          patchParamsFile = os.path.normpath(os.path.join(openJob.projectDir,patchParamsFile[9:]))
      #print 'openTask',patchParamsFile
      try:
        container.loadDataFromXml(patchParamsFile)
      except:
        print 'ERROR loading patch params file',patchParamsFile

    # If it is existing or cloned job then paramsFile exists and is loaded to container
    if paramsFile is not None:
      #print 'CProjectViewer.openTask loading',paramsFile
      try:
        container.loadDataFromXml(paramsFile)
      except:
        print 'ERROR loading params file',paramsFile
    if cloneParamsFile is not None:
      #print 'CProjectViewer.openTask loading cloned file',cloneParamsFile
      try:
        container.loadDataFromXml(cloneParamsFile)
      except:
        print 'ERROR loading cloned params file',cloneParamsFile
      try:
        if container.guiAdmin.jobTitle.isSet():
          PROJECTSMANAGER().db().updateJob(jobId=jobId,key='jobTitle',value=container.guiAdmin.jobTitle.__str__())
      except:
         print 'ERROR saving cloned jobTitle'

    # Automatically set output filenames in the container (to overwrite those from cloned job) and save to params.xml
    PROJECTSMANAGER().setOutputFileNames(container=container,projectId=openJob.projectId,
                        jobNumber=openJob.jobnumber,force=ifNewJob or (openJob.clonedFromJobId is not None ))

    self.saveStatus()
    
    # Make a guess here if there is no followJobId and it is a new job
    # and its not a clone with params already set!
    if openJob.clonedFromJobId is not None:
        followJobId = None
    elif ifNewJob and followJobId is None:
      followJobId = PROJECTSMANAGER().db().getProjectFollowFromJobId(projectId=openJob.projectId)
    #print 'openTask cloneId,ifNewJob,followJobId',openJob.clonedFromJobId,ifNewJob,followJobId

    # Draw the task input widget 
    try:
      t2 = time.time()
      if taskEditable or self.tab.currentIndex() == self.INPUT_TAB:
        taskWidget = self.inputFrame.createTaskWidget(openJob.taskname,projectId=openJob.projectid,jobId=openJob.jobId,
                                               container=container,taskEditable=taskEditable,followJobId=followJobId)
        self.connect(taskWidget,QtCore.SIGNAL('launchJobRequest'),self.launchJobRequest)
      else:
        self.inputFrame.closeTaskWidget()
    except CException as e:
      e.warningMessage(self.windowTitle(),'Error drawing task widget',parent=self)
    except Exception as e:
      import sys
      err = CErrorReport(self.__class__,999,details=str(e),exc_info=sys.exc_info())
      err.warningMessage(self.windowTitle(),'Unknown error drawing task widget\n\n'+str(e),parent=self)
    else:
      t3 = time.time()
      self.openJob = openJob
      # Update the task title bar and the 'Next' buttons
      self.titleBar.setOpenJob(self.openJob)
      self.updateTaskFrame()
      print 'opentask times total',time.time()-time1
      print 'opentask times drawing',t3-t2
      return self.openJob

    # Only get here if failed to create taskWidget properly so delete it
    try:
      taskWidget.deleteLater()
    except:
      pass

    return self.openJob

  def projectId(self):
    return self.openJob.projectId

  def launchJobRequest(self,taskName,args):
    self.emit(QtCore.SIGNAL('launchJobRequest'),taskName,args)

 

class CTaskMainWindow(QtGui.QMainWindow):
  '''Popout window for task input and report'''
  def __init__(self,parent,projectName,jobId,version=''):
    QtGui.QMainWindow.__init__(self,parent)
    self.version=version
    self.setWindowTitle(self.version+'job from project: '+projectName)
    self.setWindowIcon(CCP4WebBrowser.mainWindowIcon())
    frame = QtGui.QFrame(self)
    frame.setLayout( QtGui.QVBoxLayout())
    self.setObjectName('job'+str(jobId))
    self.titleBar = CTaskTitleBar(self)
    frame.layout().addWidget(self.titleBar)
    self.buttons = CTaskButtons(self,frame.layout(),mode=CTaskButtons.RUNONLYMODE)

    self.setCentralWidget(frame)

    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobStatusUpdated'),self.titleBar.setStatusBar)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.titleBar.setStatusBar)


  def widget(self):
    return self.centralWidget().layout().itemAt(1).widget()

  def closeEvent(self,event):
    #print 'CTaskMainWindow.closeEvent',repr(self.widget()),self.parent()
    self.deleteLater()
    self.emit(QtCore.SIGNAL('windowAboutToClose'))
    event.accept()


  def getTaskName(self):
    #print 'CTaskMainWindow.getTaskName NOT IMPLEMENTED'
    return None

  def runTask(self):
    self.widget().runTask('Local')
    #self.close()
    #self.deleteLater()

    
class CDeleteJobGui(QtGui.QDialog):
  '''Show knock-on effect of deleting a job and get user confirmation to delete all'''
    
  def __init__(self,parent=None,projectId=None,jobIdList=None,jobTreeList=[],deleteImportFiles=False,jobsToDeleteWithSelectedFiles=[],label=None,ifXtrJobs=False):
    QtGui.QDialog.__init__(self,parent)
    self.setWindowTitle('Delete jobs')
    self.projectId = projectId
    self.jobIdList = jobIdList
    self.jobTreeList = jobTreeList
    self.importFileList = []
    self.deleteImportFiles = deleteImportFiles
    self.jobsToDeleteWithSelectedFiles = jobsToDeleteWithSelectedFiles
   
    #print 'CDeleteJobGui.followOnJobs',self.followOnJobs
    
    self.setModal(True)
    self.setLayout(QtGui.QVBoxLayout())

    if len(jobsToDeleteWithSelectedFiles)>0:
      line = QtGui.QHBoxLayout()
      lab = QtGui.QLabel( self)
      lab.setPixmap( QtGui.QPixmap(os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons','list_delete.png')  ).scaled(16,16) )
      line.addWidget(lab)
      lab = QtGui.QLabel('marked jobs have files used in the current Job Input',self)
      lab.setObjectName('emphasise')
      line.addWidget(lab)
      line.addStretch(5)
      self.layout().addLayout(line)

    if ifXtrJobs:
      lab = QtGui.QLabel('Unselected jobs highlighted in pink',self)
      lab.setObjectName('emphasise')
      self.layout().addWidget(lab)

    nImportedFiles = 0
    for jobTree in self.jobTreeList:
      nImportedFiles += len(jobTree[1])

    if nImportedFiles >0:
      self.deleteImportWidget = QtGui.QCheckBox('Delete files imported by subsequent jobs - this may imply deleting additional jobs',self)
      if deleteImportFiles: self.deleteImportWidget.setCheckState(QtCore.Qt.Checked)
      self.layout().addWidget(self.deleteImportWidget)
      self.connect(self.deleteImportWidget,QtCore.SIGNAL('stateChanged(int)'),self.handleDeleteImportChanged)

    '''
    if len(importFileList)>0:
      for importId,fileName in importFileList:
        self.layout().addWidget(QtGui.QLabel(fileName,self))
        self.importFileList.append([importId,fileName])
    '''
    
    self.tree = CJobTree(self)
    self.layout().addWidget(self.tree)
    self.tree.clear()
    for jobTree in self.jobTreeList:
      self.tree.load(jobTree,jobsToDeleteWithSelectedFiles=self.jobsToDeleteWithSelectedFiles)
   
    buttonBox = QtGui.QDialogButtonBox(self)
    but = buttonBox.addButton('Delete all these jobs',QtGui.QDialogButtonBox.ApplyRole)
    but.setAutoDefault(0)
    self.connect(but,QtCore.SIGNAL('released()'),self.deleteJobs)
    but = buttonBox.addButton(QtGui.QDialogButtonBox.Cancel)
    but.setAutoDefault(0)
    self.connect(but,QtCore.SIGNAL('released()'),self.close)
    self.layout().addWidget(buttonBox)

  def handleDeleteImportChanged(self,deleteImportFiles):
    self.deleteImportFiles = deleteImportFiles
    self.followOnJobs = PROJECTSMANAGER().db().getFollowOnJobs(jobId=self.jobId,traceImportFiles=(deleteImportFiles>0))
    #print 'CDeleteJobGui.handleDeleteImportChanged',deleteImportFiles,self.followOnJobs
    self.tree.load(self.followOnJobs,jobsToDeleteWithSelectedFiles=self.jobsToDeleteWithSelectedFiles)
    
  def deleteJobs(self):
    import os
    '''
    # Beware importFilePath() returns a name for a new import so this will not work
    for importId,fileName in self.importFileList:
      filePath = PROJECTSMANAGER().importFilePath(projectId=self.projectId,baseName=fileName)
      try:
        os.remove(filePath)
        PROJECTSMANAGER().db().deleteImportFile(importId=importId)
      except:
        print 'ERROR deleting file',filePath
    '''

    for jobTree in self.jobTreeList:
      self.deleteJobs0(jobTree,deleteImportFiles=self.deleteImportFiles)
    self.emit(QtCore.SIGNAL('jobsDeleted'))
    self.close()
    
  def deleteJobs0(self,jobTree=None,deleteImportFiles=False):
    #print 'CDeleteJobGui.deleteJobs0',jobTree,deleteImportFiles
    jobId,importFiles,descendents = jobTree
    if jobId is not None:
      PROJECTSMANAGER().deleteJob(jobId=jobId,importFiles=importFiles,projectId=self.projectId,deleteImportFiles=deleteImportFiles)
   
    for childJobTree in descendents:
      self.deleteJobs0(childJobTree,deleteImportFiles=False)
      
      

class CJobTree(QtGui.QTreeWidget):
  ''' Sub-widget of CDeleteJobGui window'''
  def __init__(self,parent):
    QtGui.QTreeWidget.__init__(self,parent)
    self.setColumnCount(3)
    self.setHeaderLabels(['Job number','Task name','Status'])
    self.setColumnWidth(0,100)
    self.setColumnWidth(1,300)
    self.icon = QtGui.QIcon( QtGui.QPixmap(os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons','list_delete.png') ).scaled(16,16) )

  def load(self,jobTree,treeParentId=None,jobsToDeleteWithSelectedFiles=[]):
    #print 'CJobTree.load0',jobTree,treeParentId
    jobId,importFiles,childJobTree = jobTree
  
    jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['jobnumber','taskname','status'])

    taskTitle = TASKMANAGER().getTitle(jobInfo['taskname'])
    # Only add job once - beware a jobs could be child of multiple preceeding jobs
    # so appear in jobTree more than once
    if len(self.findItems(jobInfo['jobnumber'],QtCore.Qt.MatchExactly))==0:
      item = QtGui.QTreeWidgetItem([jobInfo['jobnumber'],taskTitle,str(jobInfo['status'])])    
      if treeParentId is None:
        self.addTopLevelItem(item)
      else:
        #treeParentId.addChild(item)
        self.addTopLevelItem(item)
        #print 'CJobTree.load0 Setting red',jobInfo['jobnumber']
        for col in 0,1,2:
          item.setBackground(col,QtGui.QBrush(QtGui.QColor('pink')))
      if jobId in jobsToDeleteWithSelectedFiles:
        item.setIcon(0,self.icon)

      for childJob in childJobTree:
        self.load(childJob,treeParentId=item,jobsToDeleteWithSelectedFiles=jobsToDeleteWithSelectedFiles)


