""" CCP4ProjectManagerGui.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 sstatusW license to address the requirements of UK law. You should have received a copy of the modified GNU Lesser General Public License along with this library. If not, copies may be downloaded from http://www.ccp4.ac.uk/ccp4license.php This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. """ """ Liz Potterton Feb 2010 - Create CCP4ProjectManager mostly as placeholder Liz Potterton May 2010 - Rename CCP4ProjectManagerGui """ ##@package CCP4ProjectManagerGui (QtGui) Project handling tools for web browser (currently just a Project menu) import functools from PyQt4 import QtGui,QtCore from core.CCP4Modules import PROJECTSMANAGER,WEBBROWSER,PREFERENCES,MIMETYPESHANDLER from core.CCP4ErrorHandling import * from qtgui import CCP4Widgets from core.CCP4Config import DEVELOPER DIAGNOSTIC = True def openProject(projectId=None,projectName=None): print 'openProject',projectId,projectName, if projectId is None: projectId = PROJECTSMANAGER().db().getProjectId(projectName=projectName) if projectId is None: return None from qtgui import CCP4ProjectViewer for window in CCP4ProjectViewer.CProjectViewer.Instances: if window.projectId() == projectId: window.show() window.raise_() return window p = CCP4ProjectViewer.CProjectViewer(projectId=projectId) p.show() p.raise_() return p def formatDate(fTime): from qtgui import CCP4ProjectWidget if fTime is None: return '' try: date = time.strftime(CCP4ProjectWidget.CTreeItemJob.DATE_FORMAT,time.localtime(int(fTime))) if date == CCP4ProjectWidget.CTreeItemJob.TODAY: date = time.strftime(CCP4ProjectWidget.CTreeItemJob.TIME_FORMAT,time.localtime(int(fTime))) elif date.split(' ')[-1] == CCP4ProjectWidget.CTreeItemJob.THISYEAR: date=date.rsplit(' ',1)[0] else: date=date.split(' ',1)[1] return date except: return '' class CNewProjectGui(QtGui.QDialog): insts = None def __init__(self,parent=None): QtGui.QDialog.__init__(self,parent) # This makes this window stay on top even when we want the file browser to be on top #self.setModal(True) self.setWindowTitle('Create a New Project') self.setLayout(QtGui.QVBoxLayout()) from core import CCP4Data from core import CCP4File from core import CCP4DataManager self.name = CCP4Data.CString(parent=self) self.directory = CCP4File.CDataFile(parent=self,qualifiers={'isDirectory' : True, 'mustExist': False, 'fromPreviousJob' : False }) MARGIN = 1 line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Name of project/folder',self)) self.nameWidget = CCP4DataManager.DATAMANAGER().widget(model=self.name,parentWidget=self) self.nameWidget.setToolTip('Project name - may be used by programs that only allow\none word of alphanumeric characters, dash or underscore.') line.addWidget(self.nameWidget) self.layout().addLayout(line) for textLine in [ "By default all projects go in the 'CCP4I2_PROJECTS' directory in your", "home area - click 'Select directory' to choose an alternative.", "Hint to organise your projects: in the 'Manage projects' window", "you can use a project as a folder and drag other projects into it" ]: line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel(textLine,self)) self.layout().addLayout(line) line = QtGui.QHBoxLayout() self.descriptionWidget = CProjectDescription(self,projectId=None,projectName='New project') line.addWidget(self.descriptionWidget) self.layout().addLayout(line) line = QtGui.QHBoxLayout() buttonBox = QtGui.QDialogButtonBox(self) but = buttonBox.addButton('Create project',QtGui.QDialogButtonBox.AcceptRole) but.setFocusPolicy(QtCore.Qt.NoFocus) but.setDefault(True) self.connect(but,QtCore.SIGNAL('released()'),self.addProject) but = buttonBox.addButton('Select directory',QtGui.QDialogButtonBox.ActionRole) but.setFocusPolicy(QtCore.Qt.NoFocus) self.connect(but,QtCore.SIGNAL('released()'),self.selectDir) but = buttonBox.addButton(QtGui.QDialogButtonBox.Cancel) but.setFocusPolicy(QtCore.Qt.NoFocus) self.connect(but,QtCore.SIGNAL('released()'),self.close) but = buttonBox.addButton(QtGui.QDialogButtonBox.Help) but.setFocusPolicy(QtCore.Qt.NoFocus) self.connect(but,QtCore.SIGNAL('released()'),self.help) line.addStretch(0.5) line.addWidget(buttonBox) line.addStretch(0.5) self.layout().addLayout(line) self.connect(self.directory,QtCore.SIGNAL('dataChanged'),self.setProjectName) self.connect(self,QtCore.SIGNAL('rejected()'),self.close) def setProjectName(self): import os if not self.directory.isSet() or self.name.isSet(): return tail = os.path.split(self.directory.__str__())[1] #print 'setProjectName',tail self.name.set(tail) self.nameWidget.updateViewFromModel() def help(self): from core import CCP4Modules CCP4Modules.WEBBROWSER().loadWebPage(helpFileName='general/tutorial' , target='projects') def clear(self): self.name.unSet() self.directory.unSet() #self.directoryWidget.updateViewFromModel() self.nameWidget.updateViewFromModel() def selectDir(self): self.nameWidget.updateModelFromView() name = self.name.get() if name is None: QtGui.QMessageBox.warning(self,'No project name','You must provide a project name') return elif PROJECTSMANAGER().projectNameStatus(name) is not 0: QtGui.QMessageBox.warning(self,'Project exists','A project of that name already exists') return rv = QtGui.QFileDialog.getExistingDirectory(caption='Select directory for saving project '+str(name)) #print 'selectDir getExistingDirectory',rv,len(rv) if rv is not None and len(rv)>0: self.addProject(str(rv)) def addProject(self,directory = None): #print 'CNewProjectGui.addProject',directory,'*' import os from core import CCP4Utils self.nameWidget.updateModelFromView() name = self.name.get() #print 'CNewProjectGui.addProject',name if name is None: QtGui.QMessageBox.warning(self,'No project name','You must provide a project name') return elif PROJECTSMANAGER().projectNameStatus(name) is not 0: QtGui.QMessageBox.warning(self,'Project exists','A project of that name already exists') return name0 = CCP4Utils.safeOneWord(name) if name0 != name: print 'Fixing name to ',name0 if directory is None: directory = CCP4Utils.getProjectDirectory() if directory is None: QtGui.QMessageBox.warning(self,'CCP4I2_PROJECTS directory','Failed creating a CCP4I2_PROJECTS directory in your home directory') return directory = os.path.join(directory,name0) else: directory = os.path.normpath(directory) if os.path.isfile(directory): QtGui.QMessageBox.warning(self,'Create project','Selected directory is a file') return elif CCP4Utils.samefile(directory,CCP4Utils.getHOME()): QtGui.QMessageBox.warning(self,'Create project','Selected directory is users home area') return elif CCP4Utils.samefile(directory,CCP4Utils.getProjectDirectory()): QtGui.QMessageBox.warning(self,'Create project','Selected directory is the CCP4 master project directory') return altName = PROJECTSMANAGER().aliasForDirectory(directory) if altName is not None: QtGui.QMessageBox.warning(self,'Directory already a project','This directory is already used by the project: '+altName) return parentProject,relpath,parentProjectId = PROJECTSMANAGER().interpretDirectory(directory) if parentProject is not None: rv = QtGui.QMessageBox.question(self,'Make sub-project', 'This is sub-directory of another project \nMake this project a sub-project of '+parentProject, QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel) if rv == QtGui.QMessageBox.Cancel: return if not os.path.exists(directory): parentDir = os.path.split(directory)[0] if not os.path.exists(parentDir): QtGui.QMessageBox.warning(self,'Directory does not exist','The parent directory for the project directory does not exist: ' +parentDir ) return ''' rv = QtGui.QMessageBox.question(self,'Make new directory','Make a new directory for the project', QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel) if rv == QtGui.QMessageBox.Cancel: return ''' try: os.mkdir(directory) except: QtGui.QMessageBox.warning(self,'Error making directory','Error making directory: '+directory) return if DEVELOPER(): projectId = PROJECTSMANAGER().createProject(projectName=name0,projectPath=directory) else: try: projectId = PROJECTSMANAGER().createProject(projectName=name0,projectPath=directory) except CException as e: e.warningMessage(parent=self) return except: QtGui.QMessageBox.warning(self,'Error creating project','Unknown error creating project') return self.descriptionWidget.save(projectId) ''' if self.xtalContents.isChecked() and pView is not None: pView.openTask(taskName='newProject') ''' self.emit(QtCore.SIGNAL('projectCreated'),projectId) pView = openProject(projectId) #print 'CNewProjectGui.addProject to close' self.close() def close(self): for child in self.findChildren(QtGui.QDialog): child.close() QtGui.QDialog.close(self) class CExportOptionsDialog(QtGui.QDialog): def __init__(self,parent=None,projectId=None,projectName=None): QtGui.QDialog.__init__(self,parent) #self.setModal(True) self.setWindowTitle('Options for saving project: '+str(projectName)) self.setLayout(QtGui.QVBoxLayout()) self.modeGroup = QtGui.QButtonGroup(self) self.modeGroup.setExclusive(True) line = QtGui.QHBoxLayout() but = QtGui.QRadioButton('Export entire project',self) self.modeGroup.addButton(but,1) but.setChecked(True) line.addWidget(but) self.layout().addLayout(line) line = QtGui.QHBoxLayout() but = QtGui.QRadioButton('Export every job after previous import/export',self) self.modeGroup.addButton(but,2) line.addWidget(but) self.layout().addLayout(line) line = QtGui.QHBoxLayout() self.impExpWidget = CImportExportListWidget(self,projectId=projectId) line.addSpacing(20) line.addWidget(self.impExpWidget) self.layout().addLayout(line) line = QtGui.QHBoxLayout() but = QtGui.QRadioButton('Select jobs to export',self) self.modeGroup.addButton(but,3) line.addWidget(but) self.layout().addLayout(line) line = QtGui.QHBoxLayout() self.selectionWidget = CCP4Widgets.CJobSelectionLineEdit(self,projectId) line.addSpacing(20) line.addWidget(self.selectionWidget) self.layout().addLayout(line) #if DEVELOPER(): if False: line = QtGui.QHBoxLayout() self.excludeI2files = QtGui.QCheckBox(self) self.excludeI2files.setChecked(True) line.addWidget(self.excludeI2files) line.addWidget(QtGui.QLabel('Exclude demo data files from CCP4I2 distribution',self)) self.layout().addLayout(line) line = QtGui.QHBoxLayout() buttonBox = QtGui.QDialogButtonBox(self) but = buttonBox.addButton('Export',QtGui.QDialogButtonBox.AcceptRole) but.setFocusPolicy(QtCore.Qt.NoFocus) self.connect(but,QtCore.SIGNAL('released()'),self.handleExport) but = buttonBox.addButton(QtGui.QDialogButtonBox.Cancel) self.connect(but,QtCore.SIGNAL('released()'),self.close) but = buttonBox.addButton(QtGui.QDialogButtonBox.Help) but.setFocusPolicy(QtCore.Qt.NoFocus) self.connect(but,QtCore.SIGNAL('released()'),self.help) line.addStretch(0.5) line.addWidget(buttonBox) line.addStretch(0.5) self.layout().addLayout(line) def handleExport(self): self.emit(QtCore.SIGNAL('export'),self.modeGroup.checkedId()) def help(self): pass class CExportJobSelection(QtGui.QLineEdit): def __init__(self,parent,projectId=None): QtGui.QLineEdit.__init__(self,parent) self.projectId = projectId self.setToolTip("Enter list of jobs e.g. '27-29,31'") def getSelection(self): errList = [] seleList = [] text = str(self.text()) splitList = text.split(',') #print 'CExportJobSelection.getSelection',text,type(text) for item in splitList: #print 'CExportJobSelection.getSelection split',item rSplit = item.split('-') if len(rSplit)==1: try: jobId = PROJECTSMANAGER().db().getJobId(projectId=self.projectId,jobNumber=item.strip()) except: errList.append(item) else: seleList.append(jobId) elif len(rSplit)==2: try: jobList = PROJECTSMANAGER().db().getJobsInRange(projectId=self.projectId,jobNumberRange=[rSplit[0].strip(),rSplit[1].strip()]) except: errList.append(item) else: if len(jobList)==0: errList.append(item) else: seleList.extend(jobList) else: errList.append(item) #print 'CExportJobSelection.getSelection', seleList,errList return seleList,errList class CImportExportListWidget(QtGui.QComboBox): def __init__(self,parent,projectId=None): QtGui.QComboBox.__init__(self,parent) self.projectId = projectId self.setEditable(False) self.load() def load(self): import time exportList = PROJECTSMANAGER().db().getProjectExportInfo(projectId=self.projectId) importList = PROJECTSMANAGER().db().getProjectImportInfo(projectId=self.projectId) #print 'CImportExportListWidget.load',len(exportList),len(importList) def formatImport(data): imDate = time.strftime( '%a %d %b %H:%M' , time.localtime(data[1])) exDate = time.strftime( '%a %d %b %H:%M' , time.localtime(data[2])) text = 'Import '+imDate+' from '+importList[iIm][3]+ ' on '+exDate return text,QtCore.QVariant(data[0]) def formatExport(data): exDate = time.strftime( '%a %d %b %H:%M' , time.localtime(data[1])) text = 'Export '+exDate return text,QtCore.QVariant(data[0]) iEx = 0 iIm = 0 while ( iEx=len(exportList): text,qVar = formatImport(importList[iIm]) iIm += 1 elif iIm>=len(importList): text,qVar = formatExport(exportList[iEx]) iEx += 1 elif importList[iIm][1]>exportList[iEx][1]: text,qVar = formatImport(importList[iIm]) iIm += 1 else: text,qVar = formatExport(exportList[iEx]) iEx += 1 self.addItem(text,qVar) def getCurrentItem(self): qVar = self.itemData(self.currentIndex()) #print 'CImportExportListWidget.getCurrentItem',qVar return qVar.toString().__str__() def getTime(self): tid = self.getCurrentItem() try: info = PROJECTSMANAGER().db().getProjectExportInfo(projectExportId=tid) except: info = {} if info.get('projectexporttime',None) is not None: return info['projectexporttime'] else: info = PROJECTSMANAGER().db().getProjectImportInfo(projectImportId=tid) return info['projectimporttime'] class CImportNewProjectDialog(QtGui.QDialog): def __init__(self,parent=None): MARGIN = 2 QtGui.QDialog.__init__(self,parent) #self.setModal(True) self.setWindowTitle('Import a New Project?') self.setLayout(QtGui.QVBoxLayout()) from core import CCP4Data from core import CCP4File from core import CCP4DataManager self.projectInfoDisplay = CProjectInfoDisplay(self) self.layout().addWidget(self.projectInfoDisplay) line = QtGui.QHBoxLayout() lab = QtGui.QLabel('The project id in the file does not match any existing project.') lab.setObjectName('emphasise') line.addWidget(lab) self.layout().addLayout(line) self.modeButs = QtGui.QButtonGroup(self) self.modeButs.setExclusive(True) lab = QtGui.QLabel('Create a new project directory',self) but = QtGui.QRadioButton('Create a new project directory',self) but.setChecked(True) but.setVisible(False) self.modeButs.addButton(but,1) self.layout().addWidget(lab) self.layout().addWidget(but) line = QtGui.QFrame() line.setLayout(QtGui.QHBoxLayout()) line.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN) line.layout().setSpacing(MARGIN) line.layout().addSpacing(30) self.directory0 = CCP4File.CDataFile(parent=self,qualifiers={ 'isDirectory' : True,'mustExist': False,'fromPreviousJob' : False }) self.directoryWidget0 = CCP4DataManager.DATAMANAGER().widget(model=self.directory0,parentWidget=self,qualifiers={'jobCombo': False, 'projectBrowser' : False} ) self.directoryWidget0.fileLineEdit.setCharWidth(60,mode='minimum') line.layout().addWidget(self.directoryWidget0) self.layout().addWidget(line) line = QtGui.QHBoxLayout() line.addSpacing(30) line.addWidget(QtGui.QLabel('Project name',self)) self.projectNameWidget = QtGui.QLineEdit(self) line.addWidget(self.projectNameWidget) self.layout().addLayout(line) """ but = QtGui.QRadioButton('Add to existing project',self) self.modeButs.addButton(but,2) self.layout().addWidget(but) line = QtGui.QHBoxLayout() line.layout().addSpacing(30) self.dbTreeWidget = CProjectsTreeWidget(self) self.dbTreeWidget.populate() line.addWidget(self.dbTreeWidget) self.layout().addLayout(line) """ line = QtGui.QHBoxLayout() buttonBox = QtGui.QDialogButtonBox(self) but = buttonBox.addButton('Import',QtGui.QDialogButtonBox.AcceptRole) self.connect(but,QtCore.SIGNAL('released()'),self.handleAccept) but.setFocusPolicy(QtCore.Qt.NoFocus) but = buttonBox.addButton(QtGui.QDialogButtonBox.Cancel) but.setFocusPolicy(QtCore.Qt.NoFocus) but.setDefault(True) self.connect(but,QtCore.SIGNAL('released()'),self.close) line.addStretch(0.1) line.addWidget(buttonBox) line.addStretch(0.1) self.layout().addLayout(line) def reset(self,importProjectInfo): import os from core import CCP4Utils self.directory0.unSet() self.projectInfoDisplay.load(importProjectInfo) self.projectNameWidget.setText(importProjectInfo['projectName']) directory = CCP4Utils.getProjectDirectory() if directory is not None: projName = CCP4Utils.safeOneWord(importProjectInfo['projectName']) projDir = os.path.join(directory,projName) #FIXME - This only puts a new name into the widget. The user is still free to override this choice with the name #of an existing file. We do not trap such behaviour. We should and forbid it somehow. if os.path.exists(projDir): projDir += "_" for i in range(10000): if not os.path.exists(projDir+str(i)): projDir = projDir+str(i) self.directory0.set(projDir) break else: self.directory0.set(projDir) def handleAccept(self): if self.modeButs.checkedId() == 1: if not self.directory0.isSet(): QtGui.QMessageBox.warning(self,'Import a New Project?','Please enter a new project directory') return projectName = self.projectName() if len(projectName)==0: QtGui.QMessageBox.warning(self,'Import a New Project?','Please enter a name for the new project') return try: projectId = PROJECTSMANAGER().db().getProjectId(projectName = projectName) except: pass else: QtGui.QMessageBox.warning(self,'Import a New Project?','There is already a project in the database called '+projectName) return else: pid = self.dbTreeWidget.selectedProjectId() if pid is None: QtGui.QMessageBox.warning(self,'Import a New Project?','Please select a project to merge with') return self.emit(QtCore.SIGNAL('import')) def mode(self): return self.modeButs.checkedId() def newDirectory(self): return self.directory0.__str__() def projectName(self): name = str(self.projectNameWidget.text()).strip() if len(name)==0: name = None return name def mergeProject(self): return self.dbTreeWidget.selectedProjectId() class CImportExistingProjectDialog(QtGui.QDialog): def __init__(self,parent=None,projectName=None): MARGIN = 2 QtGui.QDialog.__init__(self,parent) #self.setModal(True) self.setWindowTitle('Import to an Existing Project?') self.setLayout(QtGui.QVBoxLayout()) from core import CCP4Data from core import CCP4File from core import CCP4DataManager self.projectInfoDisplay = CProjectInfoDisplay(self) self.layout().addWidget(self.projectInfoDisplay) line = QtGui.QHBoxLayout() lab = QtGui.QLabel('The project id in the file matches project '+projectName) lab.setObjectName('emphasise') line.addWidget(lab) self.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget( QtGui.QLabel('Import jobs to that existing project')) self.layout().addLayout(line) line = QtGui.QHBoxLayout() buttonBox = QtGui.QDialogButtonBox(self) but = buttonBox.addButton('Import',QtGui.QDialogButtonBox.AcceptRole) self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.emit,QtCore.SIGNAL('import'))) but.setFocusPolicy(QtCore.Qt.NoFocus) but = buttonBox.addButton(QtGui.QDialogButtonBox.Cancel) but.setFocusPolicy(QtCore.Qt.NoFocus) but.setDefault(True) self.connect(but,QtCore.SIGNAL('released()'),self.close) line.addStretch(0.1) line.addWidget(buttonBox) line.addStretch(0.1) self.layout().addLayout(line) def reset(self,importProjectInfo): self.projectInfoDisplay.load(importProjectInfo) class CProjectInfoDisplay(QtGui.QFrame): def __init__(self,parent,projectInfo={}): QtGui.QFrame.__init__(self,parent) self.setObjectName('highlight') self.setLayout(QtGui.QVBoxLayout()) self.widgets = {} for item in ['projectName','hostName','userId','creationTime']: self.widgets[item] = QtGui.QLabel(self) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('The compressed file contains data for project: ',self)) line.addWidget(self.widgets['projectName']) line.addStretch(0.5) self.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Created on',self)) line.addWidget(self.widgets['hostName']) line.addWidget(QtGui.QLabel('by:',self)) line.addWidget(self.widgets['userId']) line.addWidget(QtGui.QLabel('on:',self)) line.addWidget(self.widgets['creationTime']) line.addStretch(0.5) self.layout().addLayout(line) self.load(projectInfo) def load(self,projectInfo): #print 'CProjectInfoDisplay.load',projectInfo for item in ['projectName','hostName','userId']: self.widgets[item].setText(str(projectInfo.get(item,''))) import time t = projectInfo.get('creationTime',None) if t is None: text = '' else: text = time.strftime(PROJECTSMANAGER().db().TIMEFORMAT,time.localtime(float(t))) self.widgets['creationTime'].setText(text) class CProjectsTreeWidget(QtGui.QTreeWidget): PROJECTICON = None def __init__(self,parent): QtGui.QTreeWidget.__init__(self,parent) self.projInfo = {} self.setColumnCount(2) self.expandAll() self.setHeaderLabels(['Name','Directory','Created','Last active','Tags']) self.setItemsExpandable(True) self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) self.setDragEnabled(True) self.setAcceptDrops(True) self.header().resizeSection(0,300) #self.setDragDropMode(QtGui.QAbstractItemView.InternalMove) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('projectTagsChanged'),self.handleProjectTagsChanged) def projectIcon(self): if CProjectsTreeWidget.PROJECTICON is None: from core import CCP4Utils import os fileName = os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons','project.png') CProjectsTreeWidget.PROJECTICON = QtGui.QIcon(QtGui.QPixmap(fileName)) return CProjectsTreeWidget.PROJECTICON def populate(self,args={}): self.clear() proj_dir_list0=PROJECTSMANAGER().db().getProjectDirectoryList() #print 'CProjectsView.populate',proj_dir_list proj_dir_list = [] self.projInfo = {} for pid,pName,pDir,parent,created,lastAccess in proj_dir_list0: if parent is None: proj_dir_list.append(pid) self.projInfo[pid] = { 'name' : pName, 'dir' : pDir, 'children' : [], 'created' : created, 'lastAccess' : lastAccess, 'tags' : '', 'annotation' : '' } for pid,pName,pDir,parent,created,lastAccess in proj_dir_list0: if parent is not None: self.projInfo[parent]['children'].append(pid) pTagList = PROJECTSMANAGER().db().getProjectTagList() for pid,tagText in pTagList: self.projInfo[pid]['tags'] += ','+tagText commentList = PROJECTSMANAGER().db().getProjectCommentsList() for pid,comment in commentList: self.projInfo[pid]['annotation'] = comment #print 'CProjectsTreeWidget',self.projInfo self.drawTree(projectList=proj_dir_list,parent=None) def handleProjectTagsChanged(self,projectId): #print 'CProjectsTreeWidget.handleProjectTagsChanged',projectId,self.projInfo[projectId]['name'] tagList = PROJECTSMANAGER().db().getProjectTags(projectId,tagText=True) text = '' for tid,tagText in tagList: text += ','+tagText treeWidgetItemList = self.findItems(self.projInfo[projectId]['name'],QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive,0) #print 'handleProjectTagsChanged',treeWidgetItemList,text if len(treeWidgetItemList)==1: treeWidgetItemList[0].setData(4,QtCore.Qt.DisplayRole,QtCore.QVariant(text[1:])) self.update(self.indexFromItem(treeWidgetItemList[0],4)) def makeTreeWidgetItem(self,pid): t1 = formatDate(self.projInfo[pid]['created']) t2 = formatDate(self.projInfo[pid]['lastAccess']) item = QtGui.QTreeWidgetItem([self.projInfo[pid]['name'],self.projInfo[pid]['dir'],t1,t2,self.projInfo[pid]['tags'][1:]],1001) item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsDragEnabled) item.setData(0,QtCore.Qt.UserRole,QtCore.QVariant(pid)) item.setData(0,QtCore.Qt.DecorationRole,self.projectIcon()) item.setData(0,QtCore.Qt.ToolTipRole,self.projInfo[pid]['annotation']) return item def drawTree(self,projectList=None,parent=None): for pid in projectList: item = self.makeTreeWidgetItem(pid) if parent is None: self.addTopLevelItem(item) else: parent.addChild(item) if len(self.projInfo[pid]['children'])>0: self.drawTree(self.projInfo[pid]['children'],item) def setHighlights(self,treeItem=None,highlightList=[],colour=['red','pink']): #print 'setHighlights',highlightList nHits = 0 if treeItem is None: for n in range(self.topLevelItemCount()): item = self.topLevelItem(n) childHits = self.setHighlights(item,highlightList,colour) if highlightList.count(item.data(0,QtCore.Qt.UserRole).toString().__str__()): item.setData(0,QtCore.Qt.ForegroundRole,QtGui.QBrush(QtGui.QColor(colour[0]))) nHits += 1 elif childHits>0: item.setData(0,QtCore.Qt.ForegroundRole,QtGui.QBrush(QtGui.QColor(colour[1]))) else: item.setData(0,QtCore.Qt.ForegroundRole,QtGui.QBrush(QtGui.QColor('black'))) #if childHits>0: item.setExpanded(True) nHits+= childHits self.update() else: for n in range(treeItem.childCount()): item = treeItem.child(n) childHits = self.setHighlights(item,highlightList,colour) if highlightList.count(item.data(0,QtCore.Qt.UserRole).toString().__str__()): item.setData(0,QtCore.Qt.ForegroundRole,QtGui.QBrush(QtGui.QColor(colour[0]))) nHits += 1 elif childHits>0: item.setData(0,QtCore.Qt.ForegroundRole,QtGui.QBrush(QtGui.QColor(colour[1]))) else: item.setData(0,QtCore.Qt.ForegroundRole,QtGui.QBrush(QtGui.QColor('black'))) #if childHits>0: item.setExpanded(True) nHits+= childHits #print 'setHighlights nHits',nHits return nHits def sizeHint(self): return QtCore.QSize(600,200) def mousePressEvent(self,event): if event.button() == QtCore.Qt.RightButton: self.emit(QtCore.SIGNAL('rightMousePress'),event) QtGui.QTreeWidget.mousePressEvent(self,event) def selectedItem(self): seleList = self.selectedItems() #print 'contentsTree.selectedItem',seleList if len(seleList) == 0: return None elif len(seleList) == 1: return seleList[0] else: #print 'Error in CContentsTree - more than one selected item' return seleList[0] def mimeData(self,widgetItemList): from core import CCP4Data #print 'CProjectsTreeWidget.mimeData',widgetItemList encodedData = QtCore.QByteArray() for widgetItem in widgetItemList: pid = CCP4Data.varToUUID(widgetItem.data(0,QtCore.Qt.UserRole)) #print 'CProjectsTreeWidget.mimeData',pid encodedData.append(str(pid)) # With mime type as text the data can be dropped on desktop # but the type of the data is lost mimeData = QtCore.QMimeData() mimeData.setData('project',encodedData) #mimeData.setText('project '+str(pid)) return mimeData def dragEnterEvent(self,event): if event.mimeData().hasFormat('project'): event.accept() else: event.ignore() def dragMoveEvent(self,event): dropItem = self.itemAt(event.pos().x(),event.pos().y()) #print 'dragMoveEvent',event.mimeData().hasFormat('project') #if event.mimeData().hasFormat('project') and dropItem is not None: if event.mimeData().hasFormat('project'): from dbapi import CCP4DbApi movedProject = CCP4DbApi.UUIDTYPE(event.mimeData().data('project').data()) targetProjectId = self.item2ProjectId(dropItem) if movedProject == targetProjectId: event.ignore() else: event.setDropAction(QtCore.Qt.MoveAction) event.accept() else: event.ignore() def dropEvent(self,event): targetItem = self.itemAt(event.pos().x(),event.pos().y()) #print 'CProjectsTreeWidget.dropEvent',event,targetItem if event.mimeData().hasFormat('project'): from dbapi import CCP4DbApi movedProject = CCP4DbApi.UUIDTYPE(event.mimeData().data('project').data()) targetProjectId = self.item2ProjectId(targetItem) if movedProject == targetProjectId: print 'ERROR trying to drop project in itself' return #print 'dropEvent targetProject',targetItem,targetProjectId try: PROJECTSMANAGER().db().updateProject(movedProject,key='parentProjectId',value=targetProjectId) except CException as e: print 'Failed to move project',e.report() event.setDropAction(QtCore.Qt.IgnoreAction) event.ignore() return except Exception as e: print 'Failed to move project',e event.setDropAction(QtCore.Qt.IgnoreAction) event.ignore() return #self.drawTree([movedProject],targetItem) #if targetProjectId is not None: # self.projInfo[targetProjectId]['children'].append(movedProject) self.populate() event.setDropAction(QtCore.Qt.MoveAction) event.accept() else: event.ignore() def startDrag(self,dropActions): item = self.currentItem() projectId = self.item2ProjectId(item) mimeData = self.mimeData([item]) drag = QtGui.QDrag(self) drag.setMimeData(mimeData) pixmap = item.icon(0).pixmap(18,18) drag.setHotSpot(QtCore.QPoint(9,9)) drag.setPixmap(pixmap) if drag.exec_(QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction: #self.takeItem(self.row(item)) try: if item.parent() is None: indx = self.indexOfTopLevelItem(item) self.takeTopLevelItem(indx) else: parent = item.parent() parent.removeChild(item) parentId = self.item2ProjectId(parent) self.projInfo[parentId]['children'].remove(projectId) except: pass #print 'WARNING from CProjectsTreeWidget.startDrag' def selectedProjectId(self): selList = self.selectedItems() #print 'CProjectsTreeWidget.selectedProjectId',selList if len(selList)==0: return None return self.item2ProjectId(selList[0]) def item2ProjectId(self,item): if item is None: return None from core import CCP4Data projectId = CCP4Data.varToUUID(item.data(0,QtCore.Qt.UserRole)) return projectId ''' def mimeTypes(self): return ['project'] def supportedDropActions(self): print 'supportedDropActions' return QtCore.Qt.MoveAction def dropMimeData(self,parent,index,data,action): print 'CProjectsTreeWidget.dropMimeData',parent,index,data,action return True ''' class CProjectsStatusBar(QtGui.QStatusBar): def __init__(self,parent): QtGui.QStatusBar.__init__(self,parent) self.setObjectName('statusWidget') self.setSizeGripEnabled(False) self.progressWidget =QtGui.QProgressBar(self) self.progressWidget.hide() def showMessage(self,text=None,timeout=0): #print 'CProjectsStatusBar.showMessage',text QtGui.QStatusBar.showMessage(self,text,timeout*1000) self.show() def clear(self): self.clearMessage() self.hideProgress() def showProgress(self,max=None,min=None): self.addPermanentWidget(self.progressWidget) self.progressWidget.show() self.progressWidget.setMaximum(max) def setProgress(self,value): self.progressWidget.setValue(value) def hideProgress(self): self.removeWidget(self.progressWidget) class CProjectManagerDialog(QtGui.QDialog): doneSavingJobDataSignal = QtCore.pyqtSignal() startSavingJobDataSignal = QtCore.pyqtSignal() def __init__(self,parent=None): QtGui.QDialog.__init__(self,parent) self.newProjectGui = None self.importProjectManager = None layout = QtGui.QGridLayout() self.setLayout(layout) self.projectsView = CProjectsTreeWidget(self) self.connect(self.projectsView,QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'),self.handleDoubleClick) self.projectsView.populate() self.layout().addWidget(self.projectsView,1,0) self.buttonFrame = QtGui.QFrame(self) self.buttonFrame.setLayout(QtGui.QVBoxLayout()) buttonDef = [ ['Open',self.openProject,True,True], ['Add project or folder',self.addProject,True,False], ['Edit description',self.handleEditDescription,True,False], ['Rename project',self.handleRenameProject,True,False], ['Move directory',self.handleMoveProject,True,False], ['Delete',self.handleDeleteProject,True,False], ['Cleanup files',self.handleCleanup,True,False], ['Export',self.handleExport1,True,False], ['Import',self.handleImportProject,True,False] ] if DEVELOPER(): buttonDef.append( ['Rerun test project',self.handleRerun,True,False] ) for label,slot,enabled,default in buttonDef: button =QtGui.QPushButton(self,text=label) button.setEnabled(enabled) if default: button.setDefault(True) self.buttonFrame.layout().addWidget(button) self.connect(button,QtCore.SIGNAL('clicked()'),slot) self.buttonFrame.layout().addStretch(2) self.layout().addWidget(self.buttonFrame,1,1) self.statusWidget = CProjectsStatusBar(self) self.layout().addWidget(self.statusWidget,3,0,1,2) self.searchProjectWidget = CSearchProjectWidget(self) self.layout().addWidget(self.searchProjectWidget,2,0,1,2) self.openButton = QtGui.QPushButton(self,text='Open') self.openButton.setMaximumWidth(100) self.connect(self.openButton,QtCore.SIGNAL('clicked()'),self.openProject) self.layout().addWidget(self.openButton,4,0) self.exportThread= None self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('projectsListChanged'),self.projectsView.populate) # Beware this projectUpdated signal has arguments that should be handled by the target method self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('projectUpdated'),self.projectsView.populate) def setMode(self,mode = 'all'): if mode == 'all': self.buttonFrame.show() self.statusWidget.show() self.openButton.hide() else: self.buttonFrame.hide() self.statusWidget.hide() self.openButton.show() def openSearchProjects(self): if (self,'searchProjectWidget',None) is None: self.searchProjectWidget = CSearchProjectWidget(self) self.layout().addWidget(self.searchProjectWidget,2,0,1,2) self.searchProjectWidget.show() def addProject(self): if self.newProjectGui is None: self.newProjectGui = CNewProjectGui(parent=self) #self.newProjectGui.start() self.newProjectGui.show() def handleEditDescription(self): projectId=self.projectsView.selectedProjectId() if projectId is None: return for w in self.findChildren(CProjectDescriptionDialog): if w.widget.projectId == projectId: w.show() w.raise_() return pName = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId,mode='projectname') descriptionDialog = CProjectDescriptionDialog(self,projectId,pName) descriptionDialog.show() descriptionDialog.raise_() def closeEvent(self,event): # On closing project manager delete project description children #print 'CProjectManagerDialog.closeEvent' for w in self.findChildren(CProjectDescriptionDialog): w.deleteLater() return QtGui.QDialog.closeEvent(self,event) def handleRenameProject(self): projectId=self.projectsView.selectedProjectId() if projectId is None: return pName = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId,mode='projectname') self.renameDialog = QtGui.QDialog(self) self.renameDialog.setModal(True) self.renameDialog.setWindowTitle('Rename project?') self.renameDialog.setLayout(QtGui.QVBoxLayout()) self.renameDialog.layout().addWidget(QtGui.QLabel('Rename project: '+pName,self)) self.renameWidget = QtGui.QLineEdit(self) self.renameWidget.setText(pName) self.renameDialog.layout().addWidget(self.renameWidget) butBox = QtGui.QDialogButtonBox(self) but = butBox.addButton('Rename',QtGui.QDialogButtonBox.ApplyRole) but.setDefault(False) self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.renameProject,projectId)) but = butBox.addButton(QtGui.QDialogButtonBox.Cancel) self.connect(but,QtCore.SIGNAL('released()'),self.renameDialog.close) self.renameDialog.layout().addWidget(butBox) self.renameDialog.show() self.renameDialog.raise_() def renameProject(self,projectId): self.renameDialog.hide() from core import CCP4Utils #print 'renameProject',projectId,self.renameWidget.text() name0 = self.renameWidget.text().__str__() if len(name0)==0: return name = CCP4Utils.safeOneWord(name0) if name0 != name: print 'Fixing name to ',name try: PROJECTSMANAGER().db().getProjectId(name) except: # Failed finding project of this name - good! pass else: QtGui.QMessageBox.warning(self,'Rename project','A project called '+name+' already exists') return try: PROJECTSMANAGER().db().updateProject(projectId,'projectname',name) except CException as e: e.warningMessage(parent=self,windowTitle=self.windowTitle(),message='Error changing project name') def openProject(self): projectId=self.projectsView.selectedProjectId() if projectId is not None: openProject(projectId) def handleDoubleClick(self,item,col=None): #print 'handleDoubleCLick',item,col nameVar = item.data(0,QtCore.Qt.DisplayRole) name = nameVar.toString().__str__() openProject(projectName=name) def handleExport1(self): if getattr(self,'exportThread',None) is not None: QtGui.QMessageBox.warning(self,'Export project','Please wait - another project export currently in progress') return projectId=self.projectsView.selectedProjectId() if projectId is None: #print 'handleExport calling statusBar' self.statusWidget.showMessage("Select a project before selecting 'Export'",5) return projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) if not hasattr(self,'exportOptions'): self.exportOptionsWidget = CExportOptionsDialog(self,projectId,projectInfo['projectname']) self.connect(self.exportOptionsWidget,QtCore.SIGNAL('export'),functools.partial(self.handleExport2,projectId)) self.exportOptionsWidget.show() def handleExport3(self,projectId): if getattr(self,'exportThread',None) is not None: QtGui.QMessageBox.warning(self,'Export project','Please wait - another project export currently in progress') return if projectId is None: #print 'handleExport calling statusBar' self.statusWidget.showMessage("Select a project before selecting 'Export'",5) return projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) if not hasattr(self,'exportOptions'): self.exportOptionsWidget = CExportOptionsDialog(self,projectId,projectInfo['projectname']) self.connect(self.exportOptionsWidget,QtCore.SIGNAL('export'),functools.partial(self.handleExport2,projectId)) self.exportOptionsWidget.show() progressBar = QtGui.QProgressBar() progressBar.setWindowTitle("Exporting "+str(projectInfo['projectname'])) progWin = QtGui.QWidget() layout = QtGui.QVBoxLayout() progWin.setLayout(layout) layout.addWidget(progressBar) label = QtGui.QLabel("Exporting "+str(projectInfo['projectname'])) layout.addWidget(label) def setLabelProgress(ret): job,done = ret label.setText("Saving job "+str(job)) def showProgressBar(): progressBar.setMaximum(0) progressBar.setMinimum(0) progWin.show() if hasattr(self,"exportThread"): self.connect(self.exportThread,QtCore.SIGNAL('savingJobData'),setLabelProgress) def hideProgressBar(): progWin.close() self.startSavingJobDataSignal.connect(showProgressBar) self.doneSavingJobDataSignal.connect(hideProgressBar) def handleExport2(self,projectId,mode): from core import CCP4Utils #print 'handleExport2',projectId,mode if mode == 0: return if mode == 1: after = None jobList = None if mode == 2: after = self.exportOptionsWidget.impExpWidget.getTime() jobList = None elif mode == 3: after = None jobList,errList = self.exportOptionsWidget.selectionWidget.getSelection() if len(errList)>0: errText = 'Unable to interpret selection: ' for err in errList: errText = errText +"'"+err+"' " mess = QtGui.QMessageBox.warning(self,'Export project',errText) return #if DEVELOPER(): if False: excludeI2files = self.exportOptionsWidget.excludeI2files.isChecked() else: excludeI2files = False #print 'handleExport2',after,jobList self.exportOptionsWidget.close() projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) safeName = CCP4Utils.safeOneWord(projectInfo['projectname']) from qtgui import CCP4FileBrowser from qtcore import CCP4Export self.browser = CCP4FileBrowser.CFileDialog(self, title='Save project data to compressed file', filters= ['CCP4 Project Database (*.'+CCP4Export.COMPRESSED_SUFFIX+')'], defaultSuffix=CCP4Export.COMPRESSED_SUFFIX, defaultFileName=safeName, fileMode=QtGui.QFileDialog.AnyFile ) self.connect(self.browser,QtCore.SIGNAL('selectFile'),functools.partial(self.compressProject,projectId,after,jobList,excludeI2files)) self.browser.show() def export(self,projectId,fileName): jobNumberList,errReport = PROJECTSMANAGER().db().exportProjectXml(projectId,fileName) if len(errReport)>0: errReport.warningMessage(parent=self,windowTitle='Errors exporting project',message='Some errors in exporting project to file:\n'+fileName) def compressProject(self,projectId,after=None,jobList=None,excludeI2files=False,fileName=None): #print 'CProjectManagerDialog.compressProject',after,jobList,excludeI2files,fileName self.browser.hide() self.browser.deleteLater() projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) # If there is a limited set of jobs then find the input jobs that are not output by jobs on that list inputFilesList,inputFileIdList,fromJobList,errReport = PROJECTSMANAGER().getJobInputFiles(projectDir=projectInfo['projectdirectory'],jobIdList=jobList,useDb=True,excludeI2files=excludeI2files) #print 'CProjectManagerDialog.compressProject inputFilesList,fromJobIdList',inputFilesList,fromJobIdList fromJobIdList = [] fromJobNumberList = [] for item in fromJobList: fromJobIdList.append(item[0]) fromJobNumberList.append(item[1]) import time,os dbxml = os.path.join( projectInfo['projectdirectory'],'CCP4_TMP','DATABASE'+str(int(time.time()))+'.db.xml') self.statusWidget.showMessage('Creating XML database:'+dbxml) #print 'Creating temporary database xml file in:',dbxml # exportProjectXml returns list of TOP-LEVEL jobNumbers for the export jobNumberList,errReport = PROJECTSMANAGER().db().exportProjectXml(projectId,fileName=dbxml,recordExport=True,status='exportable',after=after,jobList=jobList,inputFileList=inputFileIdList,inputFileFromJobList=fromJobIdList) #print 'CProjectManagerDialog.compressProject jobNumberList',jobNumberList if errReport.maxSeverity()>SEVERITY_WARNING: self.statusWidget.clearMessage() errReport.warningMessage('Export project','Error creating XML database file',parent=self) return #except: # jobList = [] # err = CErrorReport(self.__class__,171) # err.warningMessage(title,'Error getting project jobs from database',parent=self) #print 'compressProject',jobList if jobList is not None: directoriesList = [] else: directoriesList = ['CCP4_IMPORTED_FILES','CCP4_PROJECT_FILES'] from qtcore import CCP4Export self.exportThread = CCP4Export.ExportProjectThread(self,projectDir=projectInfo['projectdirectory'],dbxml=dbxml,target=fileName,jobList=jobNumberList,inputFilesList=inputFilesList,directoriesList=directoriesList,) self.connect(self.exportThread,QtCore.SIGNAL('savingJobData'),self.updateSavingJobData) self.connect(self.exportThread,QtCore.SIGNAL('startSavingJobData'),self.progressSavingJobData) self.connect(self.exportThread,QtCore.SIGNAL('finished()'),functools.partial(self.doneSavingJobData,projectInfo['projectname'],fileName)) self.startSavingJobDataSignal.emit() self.exportThread.start() ''' compressedFile,errReport = PROJECTSMANAGER().compressProject(projectId,fileName,after=after) if compressedFile is None: errReport.warningMessage('Exporting database','Error creating export compressed file') else: QtGui.QMessageBox.information(self,'Exporting project','Project saved as'+compressedFile) ''' def progressSavingJobData(self,numberOfJobs): self.statusWidget.showMessage('Exporting project: Saving '+str(numberOfJobs)+' jobs to compressed file') self.statusWidget.showProgress(numberOfJobs) def updateSavingJobData(self,ret): jobNumber,jobsDone = ret #print 'updateSavingJobData',jobNumber,jobsDone if jobNumber in ['IMPORT','DATABASE','PROJECT']: label = ['imported files','database file','project data'][ ['IMPORT','DATABASE','PROJECT'].index(jobNumber)] self.statusWidget.clear() if not jobsDone: self.statusWidget.showMessage('Exporting project: Saving '+label) else: self.statusWidget.showMessage('Exporting project: Finished saving '+label) else: self.statusWidget.setProgress(jobsDone) def doneSavingJobData(self,projectName,filename): if self.exportThread.errorReport.maxSeverity()>SEVERITY_WARNING: self.exportThread.errorReport.warningMessage('Saving job data','Error saving data files for export',parent=self) else: self.statusWidget.hideProgress() self.statusWidget.showMessage('Project '+str(projectName)+' saved to: '+str(filename)) self.exportThread.deleteLater() self.exportThread = None self.doneSavingJobDataSignal.emit() def handleImportProject(self): from dbapi import CCP4DbApi CCP4DbApi.CDbXml.updateInstances() if len(CCP4DbApi.CDbXml.Instances)>0: QtGui.QMessageBox.warning(self,'Import project','Another import is already in progress - please wait') return from qtgui import CCP4FileBrowser from qtcore import CCP4Export self.browser = CCP4FileBrowser.CFileDialog(self, title='Import project compressed file', filters = [ MIMETYPESHANDLER().getMimeTypeInfo("application/CCP4-compressed-db",'filter') ], defaultSuffix= MIMETYPESHANDLER().getMimeTypeInfo("application/CCP4-compressed-db",'fileExtensions')[0], fileMode=QtGui.QFileDialog.ExistingFile ) self.connect(self.browser,QtCore.SIGNAL('selectFile'),self.handleImportProject1) self.browser.show() def handleImportProject1(self,compressedFile): from qtgui import CCP4FileBrowser if hasattr(self,'browser'): try: self.browser.hide() self.browser.deleteLater() del self.browser except: pass self.statusWidget.showMessage('Checking contents of compressed file') try: xmlFile = PROJECTSMANAGER().extractDatabaseXml(compressedFile) except CException as e: e.warningMessage('Import project','Failed extracting database XML file from compressed file',parent=self) self.statusWidget.clear() except Exception as e: QtGui.QMessageBox.warning(self,'Import project','Error extracting database xml file from '+str(compressedFile)) self.statusWidget.clear() return try: from dbapi import CCP4DbApi self.dbImport = CCP4DbApi.CDbXml(db=PROJECTSMANAGER().db(),xmlFile=xmlFile) #self.dbImport.setDiagnostic(True) importProjectInfo = self.dbImport.loadProjectInfo() except: QtGui.QMessageBox.warning(self,'Import project','Error attempting to read database file in\n'+str(compressedFile)) return try: projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=self.dbImport.projectId) except: projectInfo = None #print 'handleImportProject1 importProjectInfo',importProjectInfo if projectInfo is None: # There is no matching projectId in db- query user to create new project if not hasattr(self,'importNewDialog'): self.importNewDialog = CImportNewProjectDialog(self) self.importNewDialog.reset(importProjectInfo=self.dbImport.headerInfo()) self.connect(self.importNewDialog,QtCore.SIGNAL('import'),functools.partial(self.importProject0,compressedFile)) self.importNewDialog.show() else: self.importExistingDialog=CImportExistingProjectDialog(self,projectName=projectInfo['projectname']) self.importExistingDialog.reset(importProjectInfo=self.dbImport.headerInfo()) self.connect(self.importExistingDialog,QtCore.SIGNAL('import'),functools.partial(self.importProject1,compressedFile,projectInfo['projectdirectory'],self.dbImport.projectId)) self.importExistingDialog.show() def importProject0(self,compressedFile): # Close importNewDialog if not hasattr(self,'importNewDialog'): return self.importNewDialog.close() if self.importNewDialog.mode() == 1: # Create a new project dirName = self.importNewDialog.newDirectory() projectName = self.importNewDialog.projectName() self.importNewDialog.deleteLater() del self.importNewDialog self.importProject(compressedFile,dirName,projectName=projectName) else: # Merge into existing project despite incompatible projectId projectId = self.importNewDialog.mergeProject() self.importNewDialog.deleteLater() del self.importNewDialog projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) self.dbImport.projectId = projectId self.dbImport.projectDirectory = projectInfo['projectdirectory'] #self.dbImport.projectName = self.importNewDialog.projectName() self.importProject(compressedFile=compressedFile,dirName=self.dbImport.projectDirectory,existingProject=projectId,forceProjectId=True) def importProject1(self,compressedFile,dirName,existingProject=None): self.importExistingDialog.close() self.importExistingDialog.deleteLater() del self.importExistingDialog # Append to existing project self.importProject(compressedFile=compressedFile,dirName=dirName,existingProject=existingProject) def importProject(self,compressedFile,dirName,existingProject=None,forceProjectId=False,projectName=None): import os if DIAGNOSTIC: print 'CProjectManagerDialog.importProject',compressedFile,dirName,existingProject,projectName # Load the database.xml into temporary tables in db self.dbImport.projectDirectory = dirName if projectName is not None: self.dbImport.projectName = projectName if existingProject is None: ret = self.dbImport.createProject() if DIAGNOSTIC: print 'dbImport.createProject()',ret.report() if ret.maxSeverity()>SEVERITY_WARNING: print ret.report() ret.warningMessage(parent=self,windowTitle='Error creating project in database',ifStack=False) return self.dbImport.createTempTables() if forceProjectId: self.dbImport.loadTempTable(resetProjectId=existingProject) else: self.dbImport.loadTempTable() # If loading jobs to an existing project flag up jobs in temp tables that # are already in db if existingProject is not None: self.dbImport.setExclInTempTables() # Flag imported files to be imported (there is no checking yet that they exist) self.dbImport.setExclImportedFiles() if DIAGNOSTIC: print 'CProjectManagerDialog.importProject setting Temp Tables',self.dbImport.errReport.report() if self.dbImport.errReport.maxSeverity()>SEVERITY_WARNING: if DIAGNOSTIC: print 'Error report from the import process..' if DIAGNOSTIC: print self.dbImport.errReport.report() self.dbImport.errReport.warningMessage(parent=self,windowTitle='Error loading data from project export file',ifStack=False) # Make project directory if necessary if not os.path.exists(dirName): try: os.mkdir(dirName) except: QtGui.QMessageBox.warning(self,'Import project','Failed to create directory:'+dirName) return self.statusWidget.showMessage('Unpacking project files to '+dirName) from qtcore import CCP4Export # Unpack project files from the tar file (possibly in separate thread) # Pass import thread dbImport to enable query database and flagging loaded jobs/files if DIAGNOSTIC: print 'CProjectManagerDialog.importProject creating import thread' self.importThread = CCP4Export.ImportProjectThread(self,projectDir=dirName,compressedFile=compressedFile, dbImport=self.dbImport,diagnostic=DIAGNOSTIC) #self.connect(self.importThread,QtCore.SIGNAL('extractingJobData'),self.handleImportProgress) self.connect(self.importThread,QtCore.SIGNAL('finished()'),self.doneImportProgress) errReport = self.importThread.run() if DIAGNOSTIC: print 'CProjectManagerDialog.importProject import thread running',errReport.report() self.dbImport.cleanupTempTables() #self.dbImport.listTempJobs('TempJobs after cleanup') #self.dbImport.listTempFiles('TempFiles after cleanup') #self.dbImport.listTempFileUses('TempFileUses after cleanup') stats = self.dbImport.importStats() if DIAGNOSTIC: for key,value in stats.items(): if key == 'failedFiles': if len(value)>0: print 'Failed to import files..(probably already present)' for item in value: print 'Job_'+str(item[4]),item[2] else: print 'CProjectManagerDialog.importProject stats', key,value if errReport.maxSeverity()>SEVERITY_WARNING: self.dbImport.removeTempTables() text = 'ERRORS UNPACKING DATA FILES\n' for err in errReport: text = text + err['details'] + '\n' QtGui.QMessageBox.warning(self,'Import failed',text) self.statusWidget.showMessage('Error unpacking project files to '+dirName) return self.statusWidget.showMessage('Loading project data to database') self.dbImport.importTempTables() self.dbImport.removeTempTables() self.statusWidget.showMessage('Finished importing project') self.dbImport.db.emitSignal('projectReset',{'projectId':self.dbImport.projectId}) if stats['jobsTotal']>stats['jobsImported'] or stats['filesTotal']>stats['filesImported']: text = 'Some of the jobs/files are already in the database in project '+str(self.dbImport.projectName)+'\n' + \ 'Imported '+str(stats['jobsImported'])+' new jobs from '+str(stats['jobsTotal'])+' in file \n' + \ 'and '+str(stats['filesImported'])+' new data files from '+str(stats['filesTotal'])+' in file\n' else: text = 'Successfully imported '+str(stats['jobsImported'])+' jobs and '+str(stats['filesImported'])+' data files' if stats.get('incrJobNumber',0) > 0: text = text +'\nImporting jobs '+str(stats['importMin'])+' to '+str(stats['importMax'])+' have been renumbered\n'+str(int(stats['importMin'])+int(stats['incrJobNumber']))+' to '+str(int(stats['importMax'])+int(stats['incrJobNumber'])) +' to avoid clash with existing jobs' if len(text)>0: QtGui.QMessageBox.information(self,'Import complete',text) import copy projectId = copy.deepcopy(self.dbImport.projectId) openProject(projectId=projectId) def createImportProgress(self): pass def handleImportProgress(self,ret): #print 'handleImportProgress',ret jobNo,done = ret def doneImportProgress(self): pass def repairProject(self): pass def handleDeleteProject(self): projectId=self.projectsView.selectedProjectId() if projectId is None: return projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) self.deleteDialog = QtGui.QDialog(self) #self.deleteDialog.setModal(True) self.deleteDialog.setWindowTitle('Delete project?') self.deleteDialog.setLayout(QtGui.QVBoxLayout()) self.deleteDialog.layout().addWidget(QtGui.QLabel('Delete project from database: '+projectInfo['projectname'],self)) self.deleteDirectory = QtGui.QCheckBox('Delete directory: '+projectInfo['projectdirectory'],self) self.deleteDialog.layout().addWidget(self.deleteDirectory) butBox = QtGui.QDialogButtonBox(self) but = butBox.addButton('Delete project',QtGui.QDialogButtonBox.ApplyRole) but.setDefault(False) self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.deleteProject,projectId)) but = butBox.addButton(QtGui.QDialogButtonBox.Cancel) self.connect(but,QtCore.SIGNAL('released()'),self.deleteDialog.close) self.deleteDialog.layout().addWidget(butBox) self.deleteDialog.show() self.deleteDialog.raise_() def deleteProject(self,projectId): deleteDirectory = self.deleteDirectory.isChecked() self.deleteDialog.close() if projectId is None: return e = PROJECTSMANAGER().deleteProject(projectId=projectId,deleteDirectory=deleteDirectory) if e.maxSeverity()>SEVERITY_WARNING: e.warningMessage('Deleting project',parent=self) def handleMoveProject(self): projectId=self.projectsView.selectedProjectId() #print 'handleMoveProject projectId',projectId,type(projectId) if projectId is None: return projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=projectId) self.moveProjectId = projectId self.moveDialog = QtGui.QDialog(self) self.moveDialog.setModal(True) self.moveDialog.setWindowTitle('Move project directory?') self.moveDialog.setLayout(QtGui.QVBoxLayout()) self.moveDialog.layout().addWidget(QtGui.QLabel('Move the project directory for project: '+projectInfo['projectname'],self)) self.moveDialog.layout().addWidget(QtGui.QLabel('Current directory: '+projectInfo['projectdirectory'],self)) # Mode button group self.moveMode = -1 self.moveModeGroup = QtGui.QButtonGroup(self) self.moveModeGroup.setExclusive(True) but = QtGui.QRadioButton('Move the directory and register with database',self) self.moveModeGroup.addButton(but,1) self.moveDialog.layout().addWidget(but) but = QtGui.QRadioButton('Directory is already moved - just register with database',self) self.moveModeGroup.addButton(but,0) self.moveDialog.layout().addWidget(but) self.connect(self.moveModeGroup,QtCore.SIGNAL('buttonReleased(int)'),self.handleMoveModeChange) from core import CCP4File from core import CCP4DataManager self.moveStack = QtGui.QStackedLayout() self.moveDialog.layout().addLayout(self.moveStack) self.moveStack.addWidget(QtGui.QLabel('Choose mode above',self)) self.moveDirectory0 = CCP4File.CDataFile(parent=self,qualifiers= { 'isDirectory' : True, 'mustExist' : True } ) from qtgui import CCP4FileBrowser self.moveDirectory0Widget = CCP4DataManager.DATAMANAGER().widget(model=self.moveDirectory0,parentWidget=self,qualifiers={'jobCombo': False, 'projectBrowser' : False } ) self.moveDirectory0Widget.updateViewFromModel() self.moveDirectory0Widget.fileLineEdit.setCharWidth(60,mode='minimum') self.moveStack.addWidget(self.moveDirectory0Widget) self.moveDirectory1 = CCP4File.CDataFile(parent=self,qualifiers= { 'isDirectory' : True,'mustExist' : False } ) self.moveDirectory1Widget = CCP4DataManager.DATAMANAGER().widget(model=self.moveDirectory1,parentWidget=self,qualifiers={'jobCombo': False, 'projectBrowser' : False} ) self.moveDirectory1Widget.updateViewFromModel() self.moveDirectory1Widget.fileLineEdit.setCharWidth(60,mode='minimum') self.moveStack.addWidget(self.moveDirectory1Widget) butBox = QtGui.QDialogButtonBox(self) but = butBox.addButton(QtGui.QDialogButtonBox.Apply) but.setDefault(False) self.connect(but,QtCore.SIGNAL('released()'),self.moveProject) but = butBox.addButton(QtGui.QDialogButtonBox.Cancel) self.connect(but,QtCore.SIGNAL('released()'),self.moveDialog.close) self.moveDialog.layout().addWidget(butBox) self.moveStack.setCurrentIndex(self.moveMode -1 ) self.moveDialog.show() self.moveDialog.raise_() def handleMoveModeChange(self,mode): mode = int(mode) if mode != self.moveMode: self.moveMode = mode self.moveDirectory0Widget.closeBrowser() self.moveDirectory1Widget.closeBrowser() self.moveStack.setCurrentIndex(self.moveMode + 1 ) #print 'handleMoveModeChange',self.moveMode,self.moveStack.currentIndex() def moveProject(self): import os errMess = None if self.moveMode < 0: errMess = 'Choose if moving the directory or just updating database' if self.moveMode == 0: if not self.moveDirectory0.isSet(): errMess = 'Choose directory' else: newDir = str(self.moveDirectory0) if not os.path.exists(newDir) or not os.path.isdir(newDir): errMess = 'Directory does not exist or is not directory' else: if not self.moveDirectory1.isSet(): errMess = 'Choose new directory path' else: newDir = str(self.moveDirectory1) if os.path.exists(newDir): errMess = 'New directory should not exist already' #else: # if not os.path.exists(os.path.split(newDir)[0]): # errMess = 'Parent directory for new directory must exist' if errMess is not None: QtGui.QMessageBox.warning(self,'Can not move directory',errMess) return #print 'moveProject',newDir,self.moveMode projectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=self.moveProjectId) if self.moveMode == 1: import shutil try: shutil.copytree(projectInfo['projectdirectory'],newDir) except Exception as e: QtGui.QMessageBox.warning(self,'Error moving directory',str(e)) return try: PROJECTSMANAGER().db().updateProject(self.moveProjectId,key='projectdirectory',value=newDir) except CException as e: e.warningMessage(windowTitle='Error updating database',parent=self) return except Exception as e: QtGui.QMessageBox.warning(self,'Error updating database',str(e)) return self.moveDialog.close() def handleCleanup(self): projectId=self.projectsView.selectedProjectId() #print 'handleCleanup projectId',projectId if projectId is None: ret = QtGui.QMessageBox.question(self,self.windowTitle(),'Remove temporary files from all projects?',QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel) if ret != QtGui.QMessageBox.Ok: return PROJECTSMANAGER().cleanupAllProjects(context='temporary') else: msgBox = QtGui.QMessageBox() msgBox.setWindowTitle('Cleanup files') msgBox.setText('Do you want to delete only temporary files or temporary files and intermediate data files from sub-jobs') b = msgBox.addButton(QtGui.QMessageBox.Cancel) b = msgBox.addButton('Temporary files only',QtGui.QMessageBox.ApplyRole) self.connect(b,QtCore.SIGNAL('released()'),functools.partial(self.handleCleanup2,projectId,'temporary')) b = msgBox.addButton('Temporary and intermediate files',QtGui.QMessageBox.ApplyRole) self.connect(b,QtCore.SIGNAL('released()'),functools.partial(self.handleCleanup2,projectId,'intermediate')) msgBox.exec_() def handleCleanup2(self,projectId,context): #print 'handleCleanup2',projectId,context from core import CCP4ProjectsManager cleanup = CCP4ProjectsManager.CPurgeProject(projectId = projectId) cleanup.purgeProject(context=context) def handleRerun(self): from qtgui import CCP4FileBrowser projectId=self.projectsView.selectedProjectId() if projectId is None: return self.rerunProjectId = projectId filter_list = [] self.rerunDialog = CCP4FileBrowser.CFileDialog(self,fileMode=QtGui.QFileDialog.Directory, title='Directory for rerun output') self.connect(self.rerunDialog, QtCore.SIGNAL('selectFile'), self.rerunProject) self.rerunDialog.show() def rerunProject(self,outputDirectory): import os from coore import CCP4ProjectBasedTesting from qtgui import CCP4TextViewer self.rerunDialog.close() if not os.path.exists(outputDirectory) or not os.path.isdir(outputDirectory): QtGui.QMessageBox.warning(self,'No suitable directory for project rerun','You must provide a directory in which the rerun project directory will be created') return self.projectBasedTest = CCP4ProjectBasedTesting.CProjectBasedTesting(sourceProjectList=[self.rerunProjectId],outputDirectory=outputDirectory,useCurrentDb=True) #self.projectBasedTest.start() self.projectBasedTest.runTests() logFile = self.projectBasedTest.logFiles[-1] print 'Re-running project log file:',logFile widget = WEBBROWSER().openFile(logFile) self.connect(self.projectBasedTest,QtCore.SIGNAL('reportUpdated'),WEBBROWSER().reloadFile) def handleRecover(self): # Attempt to recreate database from the contents of the project directory - no longer working ret = QtGui.QMessageBox.question(self,'Recover project',"This will attempt to restore a project to the database\n from the information in the project directory.\nAn intermediary file called my_project_dir.ccp4db.xml will be created and read automatically.\nThis file could also be edited and read with the 'Import project XML' tool.",QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel) if ret == QtGui.QMessageBox.Cancel: return from qtgui import CCP4FileBrowser self.browser = CCP4FileBrowser.CFileDialog(self, title='Recover database from the project directory', fileMode=QtGui.QFileDialog.Directory ) self.connect(self.browser,QtCore.SIGNAL('selectFile'),self.recoverProject) self.browser.show() ''' from core import CCP4File from core import CCP4DataManager self.recoverDialog = QtGui.QDialog(self) self.recoverDialog.setModal(True) self.recoverDialog.setWindowTitle('Recover project database') self.recoverDialog.setLayout(QtGui.QVBoxLayout()) self.recoverDialog.layout().addWidget(QtGui.QLabel('Recover database information from the project directory..')) self.recoverDirectory = CCP4File.CDataFile(parent=self,qualifiers= { 'isDirectory' : True, 'mustExist' : True } ) from qtgui import CCP4FileBrowser self.recoverDirectoryWidget = CCP4DataManager.DATAMANAGER().widget(model=self.recoverDirectory,parentWidget=self,qualifiers={'jobCombo': False, 'projectBrowser' : False } ) self.recoverDirectoryWidget.updateViewFromModel() self.recoverDirectoryWidget.fileLineEdit.setCharWidth(60,mode='minimum') self.recoverDialog.layout().addWidget(self.recoverDirectoryWidget) butBox = QtGui.QDialogButtonBox(self) but = butBox.addButton(QtGui.QDialogButtonBox.Apply) but.setDefault(False) self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.recoverProject,****)) but = butBox.addButton(QtGui.QDialogButtonBox.Cancel) self.connect(but,QtCore.SIGNAL('released()'),self.recoverDialog.close) self.recoverDialog.layout().addWidget(butBox) self.recoverDialog.show() self.recoverDialog.raise_() ''' def recoverProject(self,projectDir): #print 'recoverProject',projectDir import os if not os.path.exists(projectDir) or not os.path.isdir(projectDir): QtGui.QMessageBox.warning(self,'No project directory selected','You must select a project directory') return if not os.path.exists(os.path.join(projectDir,'CCP4_JOBS')): QtGui.QMessageBox.warning(self,'Not a project directory?','This does not appear to be a CCP4i2 project directory which should contain a CCP4_JOBS sub-directory') return from dbapi import CCP4DbUtils self.makeDbXml = CCP4DbUtils.CMakeProjectDbXml(self,projectDir=projectDir,projectName=os.path.split(projectDir)[-1]) self.connect(self.makeDbXml,QtCore.SIGNAL('jobLoaded'),self.statusWidget.setProgress) self.statusWidget.showMessage('Making database file from project directory') self.statusWidget.showProgress(self.makeDbXml.numberOfJobs()) errReport = self.makeDbXml.loadProject() if errReport.maxSeverity()>SEVERITY_WARNING: errReport.warningMessage(parent=self,windowTitle='Error recovering project directory', message='Some errors are reported',ifStack=False,minSeverity=SEVERITY_ERROR) return xmlFile = self.makeDbXml.saveXmlFile() if len(errReport)>0: errReport.warningMessage(parent=self,windowTitle='Project database information recovered', message='The project database information has been recovered and saved to '+xmlFile+ \ '\nThere are some warnings which can probably be ignored.\nTo seee click Show Details' ,ifStack=False) self.statusWidget.showMessage('Loading database file to database') self.importXmlDatabase(xmlFile,projectDir=projectDir) self.statusWidget.clear() self.statusWidget.removeProgress() def handleImportXmlDatabase(self): from qtgui import CCP4FileBrowser self.browser = CCP4FileBrowser.CFileDialog(self, title='Recover database from the project directory', filters= ['CCP4 Database XML (*.ccp4db.xml)'], defaultSuffix= 'xml', fileMode=QtGui.QFileDialog.ExistingFile ) self.connect(self.browser,QtCore.SIGNAL('selectFile'),self.handleRecoverDirSelected) self.browser.show() def handleRecoverDirSelected(self,xmlFile): self.browser.hide() self.importXmlDatabase(xmlFile) def importXmlDatabase(self,xmlFile,projectDir=None): from dbapi import CCP4DbApi self.dbImport = CCP4DbApi.CDbXml(db=PROJECTSMANAGER().db(),xmlFile=xmlFile) projectInfo = self.dbImport.loadProjectInfo() # Check that we have a projectId and it is not already in DB #print 'importXmlDatabase projectInfo',projectInfo if self.dbImport.projectId is None: print 'Imported project XML file does not appear to have a projectId' return try: dbProjectInfo = PROJECTSMANAGER().db().getProjectInfo(projectId=self.dbImport.projectId,mode=['projectname','projectdirectory']) except: pass else: QtGui.QMessageBox.warning(self,'Error importing database XML','There is aready a project with the same projectID and project name '+str(dbProjectInfo.get('projectname','unknown'))+' and it has project directory '+str(dbProjectInfo.get('projectdirectory','unknown'))) return if projectDir is not None: self.dbImport.projectDirectory = projectDir ret = self.dbImport.createProject() # Expect an error from createProject if the project is already in db #print 'CProjectManagerDialog.importProject from createProject',ret.report() self.dbImport.createTempTables() self.dbImport.loadTempTable() if self.dbImport.errReport.maxSeverity()>SEVERITY_WARNING: print 'Error report from the import process..' self.dbImport.errReport.warningMessage(windowTitle='Project manager- importing XML', message='Failed importing database from XML file',ifStack=False,parent=self) else: self.dbImport.setAllToImport() #self.dbImport.listTempJobs('Temp jobs') #self.dbImport.listTempFiles('Temp files') self.dbImport.importTempTables() self.dbImport.removeTempTables() openProject(projectId=self.dbImport.projectId) class CTagLineEdit( QtGui.QLineEdit ): def keyPressEvent(self,event): if event.key() in [QtCore.Qt.Key_Return,QtCore.Qt.Key_Enter]: self.emit(QtCore.SIGNAL('enterKeyPress'),event) event.accept() else: QtGui.QLineEdit.keyPressEvent(self,event) class CProjectDescription(QtGui.QFrame): TAGSPERROW = 3 TAGLABEL = 'Choose tag..' def __init__(self,parent,projectId,projectName=''): QtGui.QFrame.__init__(self,parent) self.projectId = projectId self.dbTags = PROJECTSMANAGER().db().getTagList() #print 'CProjectDescription self.dbTags',self.dbTags self.setLayout(QtGui.QGridLayout()) MARGIN = 1 self.layout().setSpacing(MARGIN) self.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN) self.layout().addWidget(QtGui.QLabel('Description of project',self),0,0,1,1) self.annotationWidget = QtGui.QTextEdit(self) self.annotationWidget.setToolTip('Enter description of project') self.layout().addWidget(self.annotationWidget,1,0,4,self.TAGSPERROW) #self.layout().addWidget(QtGui.QLabel('Tag the project..',self),5,0,1,self.TAGSPERROW) from qtgui import CCP4GuiUtils icon = CCP4GuiUtils.createIcon('list_add_grey') self.moreTagsBut = QtGui.QToolButton(self) self.moreTagsBut.setIcon(icon) #self.moreTagsBut = QtGui.QPushButton('Add row of tags',self) self.layout().addWidget(self.moreTagsBut,5,0) self.connect(self.moreTagsBut,QtCore.SIGNAL('clicked()'),self.drawTags) # Edit frame self.newTagFrame = QtGui.QFrame(self) self.newTagFrame.setLayout(QtGui.QHBoxLayout()) self.newTagFrame.layout().setSpacing(MARGIN) self.newTagFrame.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN) self.editTagLabel = QtGui.QLabel('New tag',self) self.newTagFrame.layout().addWidget(self.editTagLabel) self.newTagWidget = CTagLineEdit(self) self.newTagWidget.setToolTip('Enter new tag name') self.newTagFrame.layout().addWidget(self.newTagWidget) # Save/delete newTagBut = QtGui.QPushButton('Save',self) self.newTagFrame.layout().addWidget(newTagBut) # Tool button icon =CCP4GuiUtils.createIcon(name='gears2') but = QtGui.QToolButton(self) but.setIcon(icon) but.setToolTip('Tag handling tools') but.setPopupMode(QtGui.QToolButton.InstantPopup) but.setMenu(QtGui.QMenu(self)) but.menu().addAction('Edit tag',self.editTag) but.menu().addAction('Delete tag',self.deleteTag) but.menu().addAction('Delete unused tags',self.deleteUnusedTags) self.newTagFrame.layout().addWidget(but) self.connect(self.newTagWidget,QtCore.SIGNAL('enterKeyPress'),self.saveNewTag) self.connect(newTagBut,QtCore.SIGNAL('clicked()'),self.saveNewTag) self.layout().addWidget(self.newTagFrame,6,0,1,self.TAGSPERROW) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('tagCreated'),functools.partial(self.handleTagEditSignal,'tagCreated')) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('tagUpdated'),functools.partial(self.handleTagEditSignal,'tagUpdated')) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('tagDeleted'),functools.partial(self.handleTagEditSignal,'tagDeleted')) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('unusedTagsDeleted'),functools.partial(self.handleTagEditSignal,'unusedTagsDeleted')) self.editTagCombo = None self.deleteTagCombo = None self.blockTagWidgetChanged = False self.nTagRows = 0 self.tagWidgets = [] self.commentId = None self.drawTags() self.load() def saveNewTag(self): text = str(self.newTagWidget.text()).strip() #print 'saveNewTag',text self.newTagWidget.clear() if len(text)==0: return try: tagId = PROJECTSMANAGER().db().newTag(self.projectId,text=text) except CException as e: e.warningMessage('Save project tag','Failed saving new project tag',self) return except Exception as e: print 'Failed creating tag',e return def handleTagEditSignal(self,mode,args={}): #print 'handleTagEditSignal',mode,args self.blockTagWidgetChanged = True if mode == 'tagCreated': tagId = args['tagId'] text = args['text'] # A tagsCreated signal so can just add new tag to combo boxes self.dbTags.append([tagId,None,text]) # Load new tag to tagWidgets combos and set the first unset combo # to the new tag for tagWidget in self.tagWidgets: tagWidget.addItem(text,QtCore.QVariant(tagId)) if self.editTagCombo is not None: self.editTagCombo.addItem(text,QtCore.QVariant(tagId)) if self.deleteTagCombo is not None: self.deleteTagCombo.addItem(text,QtCore.QVariant(tagId)) loaded = False for tagWidget in self.tagWidgets: if tagWidget.currentIndex() == 0: tagWidget.setCurrentIndex(tagWidget.count()-1) loaded = True break if not loaded: # No free tag combos - create another row and set first in the row self.drawTags() self.tagWidgets[-self.TAGSPERROW].setCurrentIndex(tagWidget.count()-1) elif mode in [ 'tagDeleted', 'tagUpdated','unusedTagsDeleted']: self.dbTags = PROJECTSMANAGER().db().getTagList() comboList = [] comboList.extend(self.tagWidgets) if self.editTagCombo is not None:comboList.append(self.editTagCombo) if self.deleteTagCombo is not None:comboList.append(self.deleteTagCombo) for tagWidget in self.tagWidgets: currentTagId = tagWidget.itemData(tagWidget.currentIndex(),QtCore.Qt.UserRole).toString().__str__() tagWidget.clear() tagWidget.addItem('Choose tag..',QtCore.QVariant(0)) for tid,parentTid,text in self.dbTags: tagWidget.addItem(text,QtCore.QVariant(tid)) tagWidget.setCurrentIndex(max(0,tagWidget.findData(currentTagId,QtCore.Qt.UserRole))) self.blockTagWidgetChanged = False def drawTags(self): self.nTagRows += 1 self.layout().addItem(self.layout().takeAt(self.layout().indexOf(self.newTagFrame)),6+self.nTagRows,0,1,self.TAGSPERROW) self.layout().addItem(self.layout().takeAt(self.layout().indexOf(self.moreTagsBut)),5+self.nTagRows,0) for iC in range(self.TAGSPERROW): self.tagWidgets.append(QtGui.QComboBox(self)) self.tagWidgets[-1].setEditable(False) self.tagWidgets[-1].addItem(CProjectDescription.TAGLABEL,QtCore.QVariant(0)) self.connect(self.tagWidgets[-1],QtCore.SIGNAL('currentIndexChanged(int)'),functools.partial(self.handleTagWidgetChanged,len(self.tagWidgets)-1)) #for tid,parentTid,text,nUses in self.dbTags: # print 'drawTags',tid,parentTid,text,nUses for tid,parentTid,text in self.dbTags: #print 'drawTags',tid,parentTid,text self.tagWidgets[-1].addItem(text,QtCore.QVariant(tid)) self.layout().addWidget(self.tagWidgets[-1],4+self.nTagRows,iC) def handleTagWidgetChanged(self,widgetIndex,comboIndex): #print 'handleTagWidgetChanged',self.blockTagWidgetChanged,widgetIndex,comboIndex if self.blockTagWidgetChanged: return projectTagList = [] for tW in self.tagWidgets: #if tW.currentIndex() != 0: tagList.append((str(tW.currentText()),str(tW.data(tW.currentIndex()).toString()) )) if tW.currentIndex() != 0: tid = str(tW.itemData(tW.currentIndex()).toString()) if not tid in projectTagList: projectTagList.append(tid ) #print 'CProjectDescription.handleTagWidgetChanged',projectTagList PROJECTSMANAGER().db().resetProjectTags(self.projectId,projectTagList) def load(self): if self.projectId is not None: commentInfo = PROJECTSMANAGER().db().getCommentInfo(projectId=self.projectId) else: commentInfo = {} #print 'CProjectDescription.load',commentInfo if len(commentInfo)==0: self.annotationWidget.setPlainText('') self.commentId = None else: self.annotationWidget.setPlainText(commentInfo['comment']) self.commentId = commentInfo['commentid'] if self.projectId is not None: projectTagList = PROJECTSMANAGER().db().getProjectTags(projectId=self.projectId) else: projectTagList = [] nR = (1 + ((len(projectTagList)-1)/self.TAGSPERROW)) - self.nTagRows for iR in range(nR): self.drawTags() for iT in range(len(projectTagList)): idx = self.tagWidgets[iT].findData(QtCore.QVariant(projectTagList[iT])) if idx>=0: self.tagWidgets[iT].setCurrentIndex(idx) else: self.tagWidgets[iT].setCurrentIndex(0) for iT in range(len(projectTagList),len(self.tagWidgets)): self.tagWidgets[iT].setCurrentIndex(0) def save(self,projectId=None): if projectId is not None: self.projectId = projectId annotation = str(self.annotationWidget.toPlainText()) #print 'CProjectDescription.save',annotation if self.commentId is None: PROJECTSMANAGER().db().createComment(projectId=self.projectId,comment=annotation) else: PROJECTSMANAGER().db().updateComment(self.commentId,annotation) def deleteTag(self): if self.deleteTagCombo is None: win = QtGui.QDialog(self) win.setLayout(QtGui.QVBoxLayout()) # Select tag to delete self.deleteTagCombo = QtGui.QComboBox(self) win.layout().addWidget(self.deleteTagCombo) self.connect(self.deleteTagCombo,QtCore.SIGNAL('currentIndexChanged(int)'),self.selectDeleteTag) self.deleteTagWarning = QtGui.QLabel(self) win.layout().addWidget(self.deleteTagWarning) but = QtGui.QPushButton('Delete',self) win.layout().addWidget(but) self.connect(but,QtCore.SIGNAL('clicked()'),self.applyDeleteTag) self.deleteTagCombo.clear() self.deleteTagCombo.addItem('Delete tag..',QtCore.QVariant(0)) for tid,parentTid,text in self.dbTags: self.deleteTagCombo.addItem(text,QtCore.QVariant(tid)) self.deleteTagCombo.window().show() self.deleteTagCombo.window().raise_() def selectDeleteTag(self,indx): if indx is 0: self.deleteTagWarning.setText('') return tagId = self.deleteTagCombo.itemData(self.deleteTagCombo.currentIndex(),QtCore.Qt.UserRole).toString().__str__() taggedProjects = PROJECTSMANAGER().db().getProjectsWithTag(tagId) if len(taggedProjects)>0: text = 'Tag used: '+ taggedProjects[0][1] for tP in taggedProjects[1:]: text+= ' '+tP[1] self.deleteTagWarning.setText(text) else: self.deleteTagWarning.setText('Tag unused') def applyDeleteTag(self): if self.deleteTagCombo.currentIndex() == 0: return tagId = self.deleteTagCombo.itemData(self.deleteTagCombo.currentIndex(),QtCore.Qt.UserRole).toString().__str__() PROJECTSMANAGER().db().deleteTag(tagId) self.deleteTagCombo.window().hide() def deleteUnusedTags(self): PROJECTSMANAGER().db().deleteUnusedTags() def editTag(self): if self.editTagCombo is None: win = QtGui.QDialog(self) win.setLayout(QtGui.QVBoxLayout()) # Select tag to edit self.editTagCombo = QtGui.QComboBox(self) win.layout().addWidget(self.editTagCombo) self.connect(self.editTagCombo,QtCore.SIGNAL('currentIndexChanged(int)'),self.selectEditTag) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Change to',self)) self.editTagLineEdit = QtGui.QLineEdit(self) line.addWidget(self.editTagLineEdit) but = QtGui.QPushButton('Save',self) line.addWidget(but) self.connect(but,QtCore.SIGNAL('clicked()'),self.saveEditTag) win.layout().addLayout(line) self.editTagCombo.clear() self.editTagCombo.addItem('Edit tag..',QtCore.QVariant(0)) for tid,parentTid,text in self.dbTags: self.editTagCombo.addItem(text,QtCore.QVariant(tid)) self.editTagLineEdit.clear() self.editTagCombo.window().show() self.editTagCombo.window().raise_() def selectEditTag(self,indx): if indx == 0: self.editTagLineEdit.setText('') else: self.editTagLineEdit.setText(self.editTagCombo.currentText()) def saveEditTag(self): #print 'saveEditTag',self.editTagCombo.currentIndex() if self.editTagCombo.currentIndex() == 0: return tagId = self.editTagCombo.itemData(self.editTagCombo.currentIndex(),QtCore.Qt.UserRole).toString().__str__() new = self.editTagLineEdit.text().__str__() #print 'saveEditTag',new if len(new)==0 or new == self.editTagCombo.currentText(): return try: PROJECTSMANAGER().db().updateTag(tagId,'text',new) except Exception as e: print 'ERROR editing tag', e self.editTagCombo.window().hide() class CProjectDescriptionDialog(QtGui.QDialog): def __init__(self,parent,projectId,projectName): QtGui.QDialog.__init__(self,parent) self.setWindowTitle('Edit annotation and tags for project:'+projectName) self.setLayout(QtGui.QVBoxLayout()) self.widget = CProjectDescription(self,projectId) self.layout().addWidget(self.widget) buttonBox = QtGui.QDialogButtonBox(self) self.layout().addWidget(buttonBox) but = buttonBox.addButton(QtGui.QDialogButtonBox.Close) self.connect(but,QtCore.SIGNAL('clicked()'),self.save) def save(self): ret = self.widget.save() self.close() self.deleteLater() class CSearchProjectDialog(QtGui.QDialog): def __init__(self,parent): QtGui.QDialog.__init__(self,parent) self.setWindowTitle('Search projects') self.setLayout(QtGui.QVBoxLayout()) self.widget = CSearchProjectWidget(self) self.layout().addWidget(self.widget) line = QtGui.QHBoxLayout() line.addStretch(2) buttonBox = QtGui.QDialogButtonBox(self) button = buttonBox.addButton('Search',QtGui.QDialogButtonBox.ApplyRole) button.setDefault(True) self.connect(button,QtCore.SIGNAL('clicked()'),self.handleApply) button = buttonBox.addButton(QtGui.QDialogButtonBox.Close) self.connect(button,QtCore.SIGNAL('clicked()'),self.hide) button = buttonBox.addButton('Clear',QtGui.QDialogButtonBox.ActionRole) self.connect(button,QtCore.SIGNAL('clicked()'),self.handleClear) button = buttonBox.addButton('Help',QtGui.QDialogButtonBox.HelpRole) self.connect(button,QtCore.SIGNAL('clicked()'),self.showHelp) line.addWidget(buttonBox) line.addStretch(2) self.layout().addLayout(line) def handleApply(self): params = self.widget.get() retList = PROJECTSMANAGER().db().projectSearch(searchParams=params) #print 'CSearchProjectDialog.handleApply',retList pidList = [] for pid,name in retList: pidList.append(pid) nHighlighted = self.parent().projectsView.setHighlights(None,pidList) #print 'CSearchProjectDialog.handleApply nHighlighted',nHighlighted def handleClear(self): pass def showHelp(self,target=None): WEBBROWSER().loadWebPage(helpFileName='searchTools',target="search_projects") class CSearchProjectWidget(QtGui.QFrame): def __init__(self,parent): QtGui.QFrame.__init__(self,parent) from qtgui import CCP4GuiUtils self.setFrameShape(QtGui.QFrame.Box) self.setLineWidth(2) self.setLayout(QtGui.QVBoxLayout()) self.layout().setSpacing(2) self.layout().setContentsMargins(2,2,2,2) self.widgets = {} self.model = {} line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Highlight ',self)) for item,label in [['name','name'],['annotation','description or'],['tags','tag']]: self.widgets[item] = QtGui.QCheckBox(label,self) self.widgets[item].setChecked(True) line.addWidget(self.widgets[item]) self.widgets['textSearchMode'] = QtGui.QComboBox(self) self.widgets['textSearchMode'].setEditable(False) for lab in [ 'is','starts with','contains']: self.widgets['textSearchMode'].addItem(lab) self.widgets['textSearchMode'].setCurrentIndex(2) line.addWidget(self.widgets['textSearchMode']) self.widgets['text'] = CTagLineEdit(self) line.addWidget(self.widgets['text']) self.widgets['tagCombo'] = QtGui.QComboBox(self) self.widgets['tagCombo'].setEditable(False) self.connect(self.widgets['tagCombo'],QtCore.SIGNAL('currentIndexChanged(int)'),self.handleTagCombo) self.loadTagCombo() line.addWidget(self.widgets['tagCombo']) self.simpleButtons = QtGui.QFrame(self) line.addWidget(self.simpleButtons) self.simpleButtons.setLayout(QtGui.QHBoxLayout()) self.simpleButtons.layout().setSpacing(0) self.simpleButtons.layout().setContentsMargins(0,0,0,0) for text,icon,toolTip,action in [ [ 'Search',None , 'Highlight matching projects', self.doSearch ], [ None ,'undo' , 'Unset highlighted projects', self.clear ], [ None ,'help', 'Show help pages' , self.showHelp] , [ None ,'search-plus' , 'Show advanced search tools' ,functools.partial(self.toggleAdvanced,True) ] ]: but = QtGui.QToolButton(self) if text is not None: but.setText(text) but.setMinimumHeight(but.iconSize().height()) else: icon =CCP4GuiUtils.createIcon(name=icon) but.setIcon(icon) but.setToolTip(toolTip) self.connect(but,QtCore.SIGNAL('clicked()'),action) self.simpleButtons.layout().addWidget(but) self.layout().addLayout(line) self.connect(self.widgets['text'],QtCore.SIGNAL('enterKeyPress'),self.doSearch) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('tagCreated'),self.loadTagCombo) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('tagUpdated'),self.loadTagCombo) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('tagDeleted'),self.loadTagCombo) self.adFrame = None def loadTagCombo(self): self.widgets['tagCombo'].clear() self.widgets['tagCombo'].addItem('Tag..',QtCore.QVariant(0)) for tagId,parentId,text in PROJECTSMANAGER().db().getTagList(): self.widgets['tagCombo'].addItem(text,QtCore.QVariant(tagId)) def handleTagCombo(self,indx): if indx==0: return self.widgets['text'].setText(self.widgets['tagCombo'].currentText()) def drawAdvancedFrame(self): from core import CCP4Annotation from qtgui import CCP4AnnotationWidgets,CCP4GuiUtils self.adFrame = QtGui.QFrame(self) self.adFrame.setLayout(QtGui.QVBoxLayout()) self.adFrame.layout().setSpacing(1) self.adFrame.layout().setContentsMargins(1,1,1,1) self.adFrame.hide() self.layout().addWidget(self.adFrame) line = QtGui.QHBoxLayout() line.setSpacing(1) ''' Taken out alternative date search modes - just use active mode line.addWidget(QtGui.QLabel('and was',self)) self.widgets['dateMode'] = QtGui.QButtonGroup(self) for item,bid in [['active',1],['created',2],['last accessed',3]]: but = QtGui.QRadioButton(item,self) line.addWidget(but) self.widgets['dateMode'].addButton(but,bid) self.widgets['dateMode'].button(1).setChecked(True) ''' self.widgets['useDate'] = QtGui.QCheckBox('Project active',self) self.widgets['useDate'].setChecked(False) line.addWidget(self.widgets['useDate']) self.model['dateRange']=CCP4Annotation.CDateRange(parent=self) self.widgets['dateRange'] = CCP4AnnotationWidgets.CDateRangeView(parent=self,model=self.model['dateRange']) self.widgets['dateRange'].layout().takeAt(0).widget().deleteLater() self.widgets['dateRange'].setStyleSheet("QFrame { border : 0px };") line.addWidget(self.widgets['dateRange']) line.addStretch(2) self.adFrame.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('and is a sub-project of',self)) from qtgui import CCP4Widgets self.widgets['drop'] = QtGui.QToolButton(self) self.widgets['drop'].setIcon(QtGui.QIcon(CCP4Widgets.PIXMAPMANAGER().getPixmap('project'))) line.addWidget(self.widgets['drop']) self.widgets['parent'] = QtGui.QComboBox(self) self.widgets['parent'].setEditable(False) line.addWidget(self.widgets['parent']) line.setStretch(2,2) self.adFrame.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('and uses',self)) self.modeButtonGroup = QtGui.QButtonGroup(self) self.modeButtonGroup.setExclusive(True) butId = 0 #for item in ['task and control parameters or','imported file or','directory']: for item in ['imported file or','directory']: but = QtGui.QRadioButton(item,self) butId += 1 self.modeButtonGroup.addButton(but,butId) line.addWidget(but) line.addStretch(2) self.modeButtonGroup.button(1).setChecked(True) self.connect(self.modeButtonGroup,QtCore.SIGNAL('buttonClicked(int)'),self.handleModeChange) self.adFrame.layout().addLayout(line) self.modeStack = QtGui.QStackedLayout() self.adFrame.layout().addLayout(self.modeStack) frame = QtGui.QFrame(self) frame.setLayout(QtGui.QVBoxLayout()) frame.layout().setSpacing(2) frame.layout().setContentsMargins(2,2,2,2) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Task')) ''' #self.widgets['taskName'] = QtGui.QTreeView(self) self.widgets['taskName'] =CCP4Widgets.CTreeComboBox(self) self.taskMenu = CCP4Widgets.CTasksModel(self) self.widgets['taskName'].setModel(self.taskMenu) line.addWidget(self.widgets['taskName']) frame.layout().addLayout(line) self.modeStack.addWidget(frame) ''' frame = QtGui.QFrame(self) frame.setLayout(QtGui.QVBoxLayout()) frame.layout().setSpacing(2) frame.layout().setContentsMargins(2,2,2,2) line = QtGui.QHBoxLayout() from core import CCP4File self.model['importedFile'] = CCP4File.CDataFile(parent=self) self.widgets['importedFile'] = CCP4Widgets.CDataFileView(self,model=self.model['importedFile'], qualifiers= { 'jobCombo':False, 'autoInfoOnFileImport':False, 'browseDb' : False } ) self.widgets['importedFile'].layout().takeAt(0).widget().deleteLater() line.addWidget(QtGui.QLabel('File imported into project')) line.addWidget( self.widgets['importedFile']) line.setStretchFactor(self.widgets['importedFile'],2) frame.layout().addLayout(line) self.modeStack.addWidget(frame) frame = QtGui.QFrame(self) frame.setLayout(QtGui.QVBoxLayout()) frame.layout().setSpacing(2) frame.layout().setContentsMargins(2,2,2,2) line = QtGui.QHBoxLayout() self.model['projectDir'] = CCP4File.CDataFile(parent=self,qualifiers={'isDirectory':True, 'mustExist' : True }) self.widgets['projectDir'] = CCP4Widgets.CDataFileView(self,model=self.model['projectDir'], qualifiers= { 'jobCombo':False, 'autoInfoOnFileImport':False, 'browseDb' : False } ) self.widgets['projectDir'].layout().takeAt(0).widget().deleteLater() line.addWidget(QtGui.QLabel('Uses project directory')) line.addWidget( self.widgets['projectDir']) line.setStretchFactor(self.widgets['projectDir'],2) frame.layout().addLayout(line) self.modeStack.addWidget(frame) line =QtGui.QHBoxLayout() line.addStretch(0.5) for icon,label,action in [ ['search','Search',self.doSearch], ['help','Help',self.showHelp], ['undo','Clear',self.clear], ['search-plus','Close',functools.partial(self.toggleAdvanced,False)] ]: icon =CCP4GuiUtils.createIcon(name=icon) but = QtGui.QPushButton(icon,label,self) self.connect(but,QtCore.SIGNAL('clicked()'),action) line.addWidget(but) line.addStretch(0.1) line.addStretch(0.5) self.adFrame.layout().addLayout(line) self.load() def toggleAdvanced(self,advanced=None): #print 'toggleAdvanced',advanced,self.height() if not advanced: self.adFrame.hide() self.simpleButtons.show() else: # Save the heigth of the basic frame to reset when closing advanced if self.adFrame is None: self.drawAdvancedFrame() self.adFrame.show() self.simpleButtons.hide() def handleModeChange(self,mode): self.modeStack.setCurrentIndex(mode-1) def doSearch(self): params = self.get() retList = PROJECTSMANAGER().db().projectSearch(searchParams=params) #print 'CSearchProjectDialog.handleApply',retList pidList = [] for pid,name in retList: pidList.append(pid) self.parent().projectsView.setHighlights(None,pidList) def clear(self): self.parent().projectsView.setHighlights(None,[]) def showHelp(self,target=None): WEBBROWSER().loadWebPage(helpFileName='searchTools',target=target) def load(self): info = PROJECTSMANAGER().db().getProjectSearchInfo() #print 'load',info self.widgets['parent'].setEditable(True) self.widgets['parent'].clear() self.widgets['parent'].addItem('Not set',QtCore.QVariant(-1)) for pid,name in info['parentProjects']: self.widgets['parent'].addItem(name,QtCore.QVariant(pid)) self.widgets['parent'].setEditable(False) def get(self): ret = {} nSearch = 0 for name in [ 'name','annotation','tags']: ret[name] = self.widgets[name].isChecked() if ret[name]: nSearch+=1 ret['textSearchMode'] = self.widgets['textSearchMode'].currentIndex() if nSearch>0: ret['searchText'] = str(self.widgets['text'].text()).strip() if len(ret['searchText']) ==0: ret['searchText'] = None else: ret['searchText'] = None ''' ret['dateMode'] = ['active','created','lastAccessed'][self.widgets['dateMode'].checkedId()-1] ''' if self.adFrame is not None and self.adFrame.isVisible(): ret['dateMode'] = 'active' ret['useDate'] = self.widgets['useDate'].isChecked() self.widgets['dateRange'].updateModelFromView() ret['minTime'],ret['maxTime'] = self.model['dateRange'].epochRange() if self.widgets['parent'].currentIndex() == 0: ret['parent'] = None else: ret['parent'] = str(self.widgets['parent'].itemData(self.widgets['parent'].currentIndex()).toString()) if self.modeButtonGroup.checkedId() == 1: self.widgets['importedFile'].updateModelFromView() if self.model['importedFile'].isSet(): ret['importedFile'] = str(self.model['importedFile']) elif self.modeButtonGroup.checkedId() == 2: self.widgets['projectDir'].updateModelFromView() if self.model['projectDir'].isSet(): ret['projectDir'] = str(self.model['projectDir']) #print 'CSearchProjectWidget.get',ret return ret