""" 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 import time import os import glob import sip import shutil import functools from PyQt4 import QtGui, QtCore, Qt from PyQt4.QtSvg import QSvgWidget from core.CCP4Config import DEVELOPER from core.CCP4TaskManager import TASKMANAGER from core.CCP4Modules import QTAPPLICATION, PROJECTSMANAGER, WEBBROWSER from core.CCP4Modules import LAUNCHER, MIMETYPESHANDLER, PREFERENCES from core.CCP4Modules import JOBCONTROLLER, PIXMAPMANAGER, JOBCONTROLLERGUI from core.CCP4Modules import WORKFLOWMANAGER, CUSTOMTASKMANAGER from core.CCP4ErrorHandling import * from qtgui import CCP4ProjectWidget from qtgui import CCP4WebBrowser from dbapi import CCP4DbApi from CCP4MessageBox import CMessageBox from dbapi.CCP4DbUtils import COpenJob from core import CCP4Utils from qtgui import CCP4GuiUtils from qtgui import CCP4TaskWidget KJS_DEVINTER_ON = False DEFAULT_WINDOW_SIZE = (1400, 800) ALWAYS_SHOW_SERVER_BUTTON = False def isAlive(qobj): 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) icon=None if fileExtension.lower() == ".svg": try: 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 isNotProjectViewer(self): return False 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') 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', 'job_search', 'export_project', 'sep', 'run', 'run_remote', 'clone', 'task_help', 'references', 'export_mtz', 'view_coot', 'view_ccp4mg']) #if ALWAYS_SHOW_SERVER_BUTTON or JOBCONTROLLER().serversEnabled(): toolbar.extend(['run_remote']) toolbar.setMovable(False) toolbar.show() if jobId is not None: 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() 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')) 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) 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: from qtgui 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 CException 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''' 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): '''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): 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): 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 elif isinstance(jobStatus, dict): if not jobStatus['jobId'] == self.taskFrame.openJob.jobId: return jobStatus = jobStatus['status'] if jobStatus is None: #No job set - disable all buttons 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 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 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): 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): if mode == 'taskmenu': self.showTaskChooser(True) elif mode == 'nextproject': idx = self.Instances.index(self) + 1 if idx >= len(self.Instances): idx = 0 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) 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') 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__() # KJS : Obvious bug here. Needs sorting. 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' ] return status def testReportAvailable(self): 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): if self._dictionaryWidget is None or (not isAlive(self._dictionaryWidget)): from qtgui import CCP4ModelWidgets self._dictionaryWidget = CCP4ModelWidgets.CDictDataDialog(parent=self, projectId=self.getProject()) self._dictionaryWidget.show() self._dictionaryWidget.raise_() def setSelectedJob(self,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 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): self.handleChooseTaskAndFollowFrom(taskName) def handleChooseTaskAndFollowFrom(self, taskName, data=None): '''Open a selected task''' self.showTaskChooser(False) openJob = self.taskFrame.openTask(taskName=taskName) self.setSelectedJob(jobId=openJob.jobId) def handleJobListSelectionChange(self, fileId, jobId, pipelineJobId, detatch): '''Keep the task display in step with the selection on the project job list''' 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) 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''' if mode == CProjectViewer.INPUT_TAB and self.taskFrame.inputFrame.taskWidget is None: from core 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''' 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 from qtgui 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]) 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: from qtgui 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''' if move: shutil.move(fromFile, toFile) else: shutil.copyfile(fromFile, toFile) 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'): from core 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(self.taskFrame.openJob.jobId) except: pass oldJobId = self.taskFrame.openJob.jobId clonedJobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=oldJobId, mode=['status', '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 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) 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''' 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: PROJECTSMANAGER().db().updateJobStatus(jobId, CCP4DbApi.JOB_STATUS_FAILED) else: jobDir = PROJECTSMANAGER().jobDirectory(jobId=jobId, create=False) 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''' 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 delete 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') 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''' 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''' 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') view.report.setJobInfo(jobInfo= {'jobId' : jobId, 'projectId' : projectId}) def showBibliography(self, jobId=None, taskNameList=[]): '''Show bibliography for task''' from qtgui import CCP4BibliographyViewer if jobId is None: jobId = self.taskFrame.openJob.jobId 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): 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') return # Create an input params file for job from core 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 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) 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') if not os.path.exists(paramsFile): return None defFile = TASKMANAGER().lookupDefFile(openJob.taskname, openJob.taskversion) if defFile is None: return None from core import CCP4Container container = CCP4Container.CContainer(parent=self, definitionFile=defFile, guiAdmin=True) container.loadDataFromXml(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': 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) 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.setEnabled(openJob.status) win.centralWidget().layout().insertWidget(1, widget) win.show() win.raise_() return win def handleViewTask0(self,jobId, action=None): '''Show job files in ccp4mg or coot''' text = str(action.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''' from qtgui 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''' from qtgui 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 = [] if self.taskFrame.openJob.status == 'Pending': selectedFileDict = self.taskFrame.inputFrame.taskWidget.container.inputData.inputFilesFileIds() if len(selectedFileDict) > 0: jobsWithSelectedFiles = PROJECTSMANAGER().db().getJobSourceOfFiles(fileIdList=selectedFileDict.keys()) jobsToDeleteWithSelectedFilesSet = set(jobIdList+xtrJobIdList) & set(jobsWithSelectedFiles) for i in range(len(jobsToDeleteWithSelectedFilesSet)): jobsToDeleteWithSelectedFiles.append(jobsToDeleteWithSelectedFilesSet.pop()) 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 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 handleProjectMenuExport(self): if CCP4WebBrowser.CMainWindow.projectManagerDialog is None: from qtgui import CCP4ProjectManagerGui CCP4WebBrowser.CMainWindow.projectManagerDialog = CCP4ProjectManagerGui.CProjectManagerDialog() CCP4WebBrowser.CMainWindow.projectManagerDialog.hide() # KJS : Problem here. Non-existent functions by the looks of it. CCP4WebBrowser.CMainWindow.projectManagerDialog.handleExport3(self.taskFrame.openJob.projectId) def openManageImportFiles(self): '''Open window showing imported files for this project''' from qtgui import CCP4ImpExpWidgets widget = CCP4ImpExpWidgets.CManageImportFiles(self, projectId=self.taskFrame.openJob.projectId) widget.show() def handleCsvDownloadRequest(self, 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 from qtgui 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): sourceFile = os.path.join(PROJECTSMANAGER().makeFileName(jobId=jobId, mode='TABLES_DIR'), dataName + '.csv') 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''' 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) 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) 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) 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): 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() 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: 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) if len(requirePass) == 0: return from qtgui 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): 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 = ['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): if jobStatus is None: #No job set - disable all buttons 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']) 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): 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): self.thunk.blockSignals(True) self.title.blockSignals(True) self.history.blockSignals(True) self.annotation.blockSignals(True) 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 = '' for cid,user,time,comment in commentList: if db.timeIsToday(time): self.commentId = cid self.annotation.setPlainText(comment) else: strTime = db.timeString(time) text = text + '
' + user + ' on ' + strTime+'
\n

' + comment + '

\n' text = text + '' 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) def saveThunk(self, indx): if self.openJob.jobId is 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: 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 def handleJobUpdated(self,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): 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.taskTreeLoaded = False 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.invalidateTaskTree) self.connect(CUSTOMTASKMANAGER(), QtCore.SIGNAL('listChanged'), self.invalidateTaskTree) self.connect(PREFERENCES(), QtCore.SIGNAL('preferencesSaved'), self.invalidateTaskTree) def showEvent(self, theEvent): if not self.taskTreeLoaded: self.loadTaskTree() super(CChooseTaskFrame, self).showEvent(theEvent) def handleChooseTask(self, item, column=None): try: taskName = str(item.data(0, 101).toString()) except: return if len(taskName) == 0: return self.emit(QtCore.SIGNAL('taskClicked'), taskName) def handleChooseTask0(self): self.handleChooseTask(item=self.taskTree.currentItem()) def invalidateTaskTree(self): self.taskTreeLoaded = False if self.isVisible(): self.loadTaskTree() def loadTaskTree(self): compact = PREFERENCES().COMPACT_TASK_MENU self.taskTree.clear() no_win = ["arcimboldo", "morda_i2", "fragon"] for module, title, taskList in TASKMANAGER().taskTree(): if module == "developer_tools" and not KJS_DEVINTER_ON: continue 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: if sys.platform == "win32" and (taskName in no_win): continue #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.taskTreeLoaded = True #self.taskTree.expandAll() 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') 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): return QtCore.Qt.CopyAction def dropMimeData(self, parent, index, data, action): return True def mimeTypes(self): typesList = QtCore.QStringList() for item in MIMETYPESHANDLER().mimeTypes.keys(): typesList.append(item) typesList.append('jobid') return typesList def dragEnterEvent(self, event): if event.mimeData().hasFormat('FollowFromJob'): event.accept() else: event.ignore() def dragMoveEvent(self,event): dropItem = self.itemAt(event.pos().x(), event.pos().y()) if event.mimeData().hasFormat('jobid'): event.setDropAction(QtCore.Qt.CopyAction) event.accept() else: event.ignore() def dropEvent(self,event): targetItem = self.itemAt(event.pos().x(), event.pos().y()) if event.mimeData().hasFormat('FollowFromJob'): taskName = str(targetItem.data(0, 101).toString()) data = str(event.mimeData().data('FollowFromJob').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. 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): # 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()] 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 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) from qtgui 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 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 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 if self.generator is None: from report import CCP4ReportGenerator self.generator = CCP4ReportGenerator.CReportGenerator(jobId=openJob.jobId,jobStatus=openJob.status,jobNumber=openJob.jobNumber) self.connect(self.generator,QtCore.SIGNAL('FinishedPictures'),self.handleMgFinished) try: reportFile, newPageOrNewData = self.generator.makeReportFile() except CException as e: if e.maxSeverity()>SEVERITY_WARNING: e.warningMessage(windowTitle=self.parent().windowTitle(),message='Failed creating job report',parent=self) except Exception as e: QtGui.QMessageBox.warning(self,self.parent().windowTitle(),'Unknown error creating report file for job number '+str(openJob.jobNumber)) 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): from qtgui import CCP4TextViewer from report import 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 def handleLoadFinished(self,ok): if not ok: return self.setReportLinks(self.webView.report.topFolds) def handleMgFinished(self,jobId): if jobId == self.openJob.jobId: self.webView.attemptImageLoad() def handleFileChanged(self,jobId,outputXmlFile): 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): if status in ['Running']: self.addWatchFile(self.openJob) elif status in ['Finished','Failed']: FILEWATCHER().removeJob(self.openJob.jobId) #self.showOutput(redo=True) def handleWorkflowJobStatusUpdated(self,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 # 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') 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)) 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) if taskWidgetClass is not None: if DEVELOPER(): 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? from qtgui 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() 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 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() 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 container.removeUnsetListItems() ifImportFile, errors = PROJECTSMANAGER().importFiles(jobId=jobId, container=container) rv = self.makeJobInputFile(jobId) if not rv: QtGui.QMessageBox.warning(self, str(self.taskWidget.title()), 'Job failed writing input parameters file') return # This is used in the 'workflow' automation subJobWidgets = getattr( self.taskWidget, 'subJobTaskWidgets', {}) for jobName, taskWidget in subJobWidgets.items(): taskWidget.saveToXml() # Preceeding job concept not now used to track job - this is pretty much redundant preceedingjobId = self.taskWidget.getContainer().guiAdmin.followFrom PROJECTSMANAGER().db().updateJob(jobId=jobId, key='preceedingjobid',value=preceedingjobId.pyType()) #Record input files in database PROJECTSMANAGER().db().gleanJobFiles(jobId=jobId, container=container,projectId=projectId, roleList=[CCP4DbApi.FILE_ROLE_IN]) 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): if DEVELOPER(): PROJECTSMANAGER().runInternalTask(jobId=self.taskWidget.jobId(), projectId=self.taskWidget.projectId(), taskName=self.taskWidget.taskName()) else: try: PROJECTSMANAGER().runInternalTask(jobId=self.taskWidget.jobId(), projectId=self.taskWidget.projectId(), taskName=self.taskWidget.taskName()) except Exception as e: err = CException(self.__class__,101,taskName,str(e)) 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 container = self.taskWidget.container taskName = self.taskWidget.taskName() jobId = self.taskWidget.jobId() projectId=self.taskWidget.projectId() if isinstance(self.taskWidget,CCP4TaskWidget.CTaskWidget) and PREFERENCES().TASK_WINDOW_LAYOUT == 'FOLDER': scrollDisp = self.taskWidget.getScrollDisplacement() folderOpenStatus = self.taskWidget.widget.getFolderOpenStatus() 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 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, jobId=None): if self.taskWidget is None: return False #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 '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'): 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) 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: from qtcore 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') # ? 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): self.title.setText(openJob.title) self.jobId = openJob.jobId self.setStatusBar({'jobId' : openJob.jobId, 'status' : openJob.info['status'] } ) def setStatusBar(self,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: 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,openJob=None): if hasattr(openJob,"jobId"): self.inputFrame.makeJobInputFile(openJob.jobId) else: self.inputFrame.makeJobInputFile() from dbapi import CCP4DbUtils if openJob is None: openJob = self.openJob if openJob.jobId is not None: try: CCP4DbUtils.makeJobBackup(jobId=openJob.jobId,projectName=openJob.projectName) except Exception as 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): 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) 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): 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()) 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): from core import CCP4Container,CCP4File #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: CCP4File.cloneI2XmlFile(cloneParamsFile,paramsFile,{ 'jobId' : str(openJob.jobnumber) }, taskFrame=self, taskName=taskName ) openJob.clonedFromJobId = cloneJobId #print 'CTaskFrame.openTask cloneFromJobId',cloneJobId, openJob.clonedFromJobId except: print 'ERROR cloning params file',cloneParamsFile paramsFile = None else: splitCloneParamsFile = os.path.split(cloneParamsFile) subJobParamsFiles = glob.glob(os.path.join(splitCloneParamsFile[0],'job_*_'+splitCloneParamsFile[1])) for subFile in subJobParamsFiles: CCP4File.cloneI2XmlFile(subFile,os.path.join(os.path.split(paramsFile)[0],os.path.split(subFile)[1]),{ 'jobId' : str(openJob.jobnumber) }, taskFrame=self, taskName=taskName ) # 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: # 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:])) 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: #MAJOR CHECKME SJM why on earth would I load clonedParamsFilethis if I already used paramsFile above? if cloneParamsFile is not None and paramsFile is None: container.loadDataFromXml(cloneParamsFile) if cloneJobId is not None and container.guiAdmin.get('jobTitle') is not None: jobTitle = PROJECTSMANAGER().db().getJobInfo(jobId=cloneJobId,mode='jobtitle') if jobTitle is not None: container.guiAdmin.jobTitle.set(jobTitle) 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(openJob=openJob) # 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) # 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: 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): self.deleteLater() self.emit(QtCore.SIGNAL('windowAboutToClose')) event.accept() def getTaskName(self): return None def runTask(self): self.widget().runTask('Local') 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 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)) self.tree.load(self.followOnJobs,jobsToDeleteWithSelectedFiles=self.jobsToDeleteWithSelectedFiles) def deleteJobs(self): ''' # 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): 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=[]): 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) 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)