"""
     qtgui/CCP4Widgets.py: CCP4 Gui Project
     Copyright (C) 2010 University of York

     This library is free software: you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public License
     version 3, modified in accordance with the provisions of the 
     license to address the requirements of UK law.
 
     You should have received a copy of the modified GNU Lesser General 
     Public License along with this library.  If not, copies may be 
     downloaded from http://www.ccp4.ac.uk/ccp4license.php
 
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU Lesser General Public License for more details.
"""

##@package CCP4Widgets (QtGui) Collection of widgets for simple data types
from PyQt4 import QtGui,QtCore,QtSvg
import CCP4Data
import CCP4ModelData
import CCP4XtalData
import CCP4File
from CCP4Modules import PROJECTSMANAGER,PIXMAPMANAGER,QTAPPLICATION,TASKMANAGER,LAUNCHER,WEBBROWSER,PREFERENCES,MIMETYPESHANDLER,COMFILEPATCHMANAGER

from CCP4ErrorHandling import *
import functools,sys

ICONBUTTONSIZE=24
DRAGICONSIZE=16
CONTRASTCOLOUR = '#9BFFFF'
ERRORCOLOUR = '#FF0000'


class CBaseWidget:
  CHARSIZE = None
  STRETCH = -1
  TABLE_EDIT_MODE = None
  def __init__(self,dragType=None):
    self._dragType = dragType
    if self.dragType() is not None: self.setAcceptDrops(1)
    if CBaseWidget.CHARSIZE is None:
      someText = "/99/Z/999.2/HG51"
      dummy = QtGui.QLineEdit()
      fn = dummy.font()
      fm = QtGui.QFontMetrics(fn)
      CBaseWidget.CHARSIZE = fm.width(someText)/len(someText)

  def setCharWidth(self,charWidth=None,mode='fixed'):
    #print 'CBaseWidget.setCharWidth',self,charWidth
    #Add small extra (2) cos widgets never seem wide enough
    if charWidth is not None and charWidth>0:
      if mode in ('fixed','minimum'):
        self.setMinimumWidth((charWidth+2)*self.CHARSIZE)
      if mode in ('fixed','maximum'):
        self.setMaximumWidth((charWidth+2)*self.CHARSIZE)

  def dragType(self):
    #print 'CBaseWidget.dragType',self.objectName(),self._dragType
    if self._dragType is None:
      return None
    elif not isinstance(self._dragType,list):
      return self._dragType
    else:
      return self._dragType[0]
  
  def dragTypeList(self):
    #print 'CBaseWidget.dragTypeList',self.objectName(),self._dragType
    if self._dragType is None:
      return []
    elif not isinstance(self._dragType,list):
      return [self._dragType]
    else:
      return self._dragType

  def dropTypes(self):
    if getattr(self,'model',None) is not None:
      return [str(self.model.__class__.__name__)[1:]]
    else:
      return []
  
  def getFilesFromMimeData(self,mimeData):
    fileList = []
    if mimeData.hasUrls():
      urlList = mimeData.urls()
      for url in urlList:
        path = url.path().__str__().encode('ascii','ignore')
        mimeTypeList=MIMETYPESHANDLER().classListFromFileName(path)
        #print 'getFilesFromMimeData mimeTypeList',mimeTypeList
        for mimeType in mimeTypeList:
          fileList.append([path,mimeType.encode('ascii','ignore')])
    #print 'getFilesFromMimeData',fileList
    return fileList

  def makeDragEtree(self,path,mimeType):
    from lxml import etree
    import os
    rel,base = os.path.split(path)
    fileEle = etree.Element(str(mimeType).encode('ascii','ignore'))
    ele = etree.Element('relPath')
    ele.text = rel
    fileEle.append(ele)
    ele = etree.Element('baseName')
    ele.text = base
    fileEle.append(ele)
    text = etree.tostring(fileEle)
    #print 'makeDragEtree',text
    return text
    

  def acceptMimeData(self,mimeData):
    #print 'CBaseData.acceptMimeData', repr(self),self.dragTypeList(), self.dropTypes()
    #for item in mimeData.formats(): print 'CBaseData.acceptMimeData data:',str(item),mimeData.data(item)
    dragTypeList = self.dragTypeList()
    if dragTypeList is not None:
      for dragType in dragTypeList:
        if mimeData.hasFormat(dragType):
          return  str(mimeData.data(dragType))
      if len(self.dropTypes())>0:
        for item in self.dropTypes():
          if mimeData.hasFormat(item): return  str(mimeData.data(item))
      elif isinstance(self,CIconButton) and len(self.parent().dropTypes())>0:
        #print 'acceptMimeData parent dropTypes',self.parent().dropTypes()
        for item in self.parent().dropTypes():
          if mimeData.hasFormat(item):
            print 'dropTypes',item,mimeData.data(item),'*'
            return  str(mimeData.data(item))
      elif mimeData.hasFormat('jobId'):
        dropText = str(mimeData.data('jobId'))
        from lxml import etree
        tree = etree.fromstring(dropText)
        for groupName in ['outputFiles','outputData']:
          groupEle = tree.find(groupName)
          if groupEle is not None:
            for ele in groupEle:
              if ele.tag in dragTypeList:
                return etree.tostring(ele)
    fileList = self.getFilesFromMimeData(mimeData)
    #print 'acceptMimeData fileList',fileList,self.dropTypes()
    for path,mimeType in fileList:
      if mimeType in self.dragTypeList():
        return self.makeDragEtree(path,mimeType)
      elif len(self.dropTypes())>0:
        for item in self.dropTypes():
          if mimeType == item : return self.makeDragEtree(path,mimeType)
    return None

  def dragEnterEvent(self,event):
    #print 'CBaseWidget.dragEnterEvent',
    #print self.dragType(),event.mimeData().hasFormat(self.dragType())
    if self.acceptMimeData(event.mimeData()) is not None:
      event.accept()
    else:
      event.ignore()

  def dragMoveEvent(self,event):
    #print 'CBaseWidget.dragMoveEvent'
    if self.acceptMimeData(event.mimeData()) is not None:
      event.setDropAction(QtCore.Qt.CopyAction)
      event.accept()
    else:
      event.ignore()

  def dropEvent(self,event):
    dropData = self.acceptMimeData(event.mimeData())
    if dropData is not None:
      #print 'CBaseWidget.dropEvent',dropData,type(dropData),type(self)
      self.emit(QtCore.SIGNAL('acceptDropData'),dropData)
      event.setDropAction(QtCore.Qt.CopyAction)       
      event.accept()
    else:
      event.ignore()

  def makeToolTip(self,qualis):
    if 'toolTip' in qualis and qualis['toolTip'] is not None and qualis['toolTip'] is not NotImplemented:
      tip = qualis['toolTip'] + '.'
    else:
      tip = ''
    for key in ['default','min','max']:
      if key in qualis and qualis[key] is not None and qualis[key] is not NotImplemented:
        tip = tip + ' '+key+':'+str(qualis[key])
    
    return tip

  def updateFrameLayout(self):
    '''
    Find the parent CFolderTaskWidget or CContainerViewer and update their layout
    '''
    import CCP4TaskWidget,CCP4ContainerView
    widget = self
    while  isinstance(widget,QtGui.QWidget):
      if isinstance(widget,(CCP4TaskWidget.CFolderTaskWidget,CCP4ContainerView.CContainerView)):
        #print 'CBaseWidget.updateFrameLayout',widget
        layout = widget.layout()
        if layout is not None: widget.layout().update()
        return
      else:
        widget = widget.parent()
      
  def parentTaskWidget(self):
    import CCP4TaskWidget,CCP4ContainerView,CCP4ProjectViewer
    w = self.parent()
    while w is not None and isinstance(w,QtGui.QWidget):
      #if isinstance(w,(CCP4TaskWidget.CTaskWidget,CCP4ContainerView.CContainerView,CCP4ProjectViewer.CTaskFrame)):
      if isinstance(w,(CCP4TaskWidget.CTaskWidget,CCP4ContainerView.CContainerView)):
        return w
      else:
        w = w.parent()
    return None

  def parentTaskFrame(self):
    import CCP4ProjectViewer
    w = self.parent()
    while w is not None and isinstance(w,QtGui.QWidget):
      #print 'Widget hierarchy type',type(w)
      #if isinstance(w,(CCP4TaskWidget.CTaskWidget,CCP4ContainerView.CContainerView,CCP4ProjectViewer.CTaskFrame)):
      if isinstance(w,(CCP4ProjectViewer.CTaskFrame)):
        return w
      else:
        w = w.parent()
    return None

  def reset(self):
    pass

  def validate(self,isValid=None,**kw):
    pass

  
class CPixmapManager:
    '''Load all icon source files from qticons and return QPixmap when requested'''
    insts = None
    SIZE = 32
    
    def __init__(self):
      CPixmapManager.insts = self
      self.pixmaps = {}
      self.loadPixmaps()

    def loadPixmaps(self,dir='',subDirectories=[''],width=32,height=0):
      import os,glob,string
      if not dir:
          dir = os.path.join(os.environ['CCP4I2_TOP'],'qticons')
          subDirectories = ['']
      #print "CPixmapManager.loadPixmaps",dir
    
      self.pixmaps = {}
      if height<=0: height = width

      for subd in subDirectories:
        for fileName in glob.glob(os.path.join(dir,subd,'*.png')):
          #print 'CPixmapManager.loadPixmaps',fileName
          name = os.path.split(fileName)[-1][0:-4]
          # Change to Qt4.7 seems to break instantiating QIcons with a QPixmap
          # and seems to be OK instatiated with the filename
          #print 'loadPixmaps',name
          #pixmap = QtGui.QPixmap(file)
          #self.pixmaps[name] = pixmap.scaled(CPixmapManager.SIZE,CPixmapManager.SIZE)
          self.pixmaps[name] = fileName
        for fileName in glob.glob(os.path.join(dir,subd,'*.svg')):
          name = os.path.split(fileName)[-1][0:-4]
          self.pixmaps[name] = self.loadSvg(fileName)
          #self.pixmaps[name] = fileName

    def loadSvg(self,fileName):
     svg = QtSvg.QSvgRenderer()
     svg.load(fileName)
     pixmap = QtGui.QPixmap(CPixmapManager.SIZE,CPixmapManager.SIZE)
     pixmap.fill(QtGui.QColor(0,0,0,0))
     painter = QtGui.QPainter(pixmap)
     svg.render(painter)
     painter.end()
     return pixmap

    def getPixmap(self,name):
       #print 'getPixmap',name,self.pixmaps.has_key(name)
       if self.pixmaps.has_key(name):
           return self.pixmaps[name]
       elif self.pixmaps.has_key('blank'):
           return self.pixmaps['blank']
       else:
           return None

class CIntValidator(QtGui.QIntValidator):
   '''Reimplementation of Qt validator'''
   def __init__(self,bottom=None,top=None,parent=None):
       QtGui.QIntValidator.__init__(self,bottom,top,parent)

   def validate(self,input,pos):
       if len(input) == 0: return (QtGui.QValidator.Acceptable,pos)
       return QtGui.QIntValidator.validate(self,input,pos)
       
     
class CDoubleValidator(QtGui.QDoubleValidator):
   '''Reimplementation of Qt validator'''
   def __init__(self,bottom=None,top=None,decimals=3,parent=None):
       if bottom is None: bottom = float('-inf')
       if top is None: top = float('inf')
       QtGui.QDoubleValidator.__init__(self,bottom,top,decimals,parent)
       self.setNotation(QtGui.QDoubleValidator.StandardNotation)

   def validate(self,input,pos):
       if len(input) == 0: return (QtGui.QValidator.Acceptable,pos)
       try:
         float(input)
       except:
         return QtGui.QDoubleValidator.validate(self,input,pos)
       #if len(input)>0 and float(input)>self.top() or float(input)<self.bottom():
       #   return QtGui.QDoubleValidator.validate(self,QtCore.QString(10*float(input)),pos)
       print 'CDoubleValidator.validate',input,type(input)
       return QtGui.QDoubleValidator.validate(self,input,pos)


class CClickableFrame(QtGui.QFrame):
    def __init__(self,parent):
      QtGui.QFrame.__init__(self,parent)

    def mouseReleaseEvent(self,event):
      # Reimplementing QWidget.mouseReleaseEvent because
      # QFrame does not have required signal
      if event.button() ==  QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('contextMenu'),event.globalX(),event.globalY())
      QtGui.QFrame.mouseReleaseEvent(self,event)
        


class CLabel(QtGui.QLabel,CBaseWidget):

    def __init__(self,parent=None,qualifiers={},**kw):
      qualis = {}
      qualis.update(qualifiers)
      qualis.update(kw)
      QtGui.QLabel.__init__(self,parent)
      CBaseWidget.__init__(self,dragType=qualis.get('dragType',None))
      if qualis.has_key('text'):
        if qualis['text'] is not None:
          self.setText(qualis['text'])
      self.setCharWidth(qualis.get('charWidth',None))
      if not qualis.get('editable',True):
        #styleSheet ='QLabel {font-weight:bold}'
        styleSheet =  qualis.get('styleSheet',None)
        outlineFrame = True
      else:
        styleSheet = qualis.get('styleSheet',None)
        #print 'CLabel.__init__ styleSheet',styleSheet
        outlineFrame = qualis.get('outlineFrame',None)
      if styleSheet is not None: self.setStyleSheet(styleSheet)
      if outlineFrame is not None: self.setFrameStyle(QtGui.QFrame.StyledPanel|QtGui.QFrame.Plain)

        
    def mousePressEvent(self,event):
      if event.button() == QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('rightMousePress'),event)
        event.accept()
      else:
        QtGui.QLabel.mousePressEvent(self,event)
        
    def mouseReleaseEvent(self,event):
      if event.button() == QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('rightMouseRelease'),event)        
      QtGui.QLabel.mouseReleaseEvent(self,event)

    def setValue(self,text=None):
      if text is None:
        self.setText('')
      else:
        self.setText(str(text))

    def getValue(self):
      return self.text().__str__()       
        
class CItalicLabel(CLabel):
  def __init__(self,text=None,parent=None):
    CLabel.__init__(self,parent)
    if text is not None: self.setText(text)
    # use style sheet to set style for QLabel#italic
    self.setObjectName('italic')

class CBoldLabel(CLabel):
  def __init__(self,text=None,parent=None):
    CLabel.__init__(self,parent)
    if text is not None: self.setText(text)
    # use style sheet to set style for QLabel#bold
    self.setObjectName('bold')

class CPushButton(QtGui.QPushButton,CBaseWidget):
  
    def __init__(self,parent=None,qualifiers={},**kw):
      qualis = {}
      qualis.update(qualifiers)
      qualis.update(kw)
      QtGui.QPushButton. __init__(self,parent)
      if qualis.has_key('text') and qualis['text'] is not None:
        self.setText(qualis['text'])
      tip = self.makeToolTip(qualis)
      if len(tip)>0: self.setToolTip(tip)
        
    def mousePressEvent(self,event):
      if event.button() == QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('rightMousePress'),event)
        event.accept()
      else:
        QtGui.QPushButton.mousePressEvent(self,event)


class CLineEdit(QtGui.QLineEdit,CBaseWidget):

    def __init__(self,parent=None,qualifiers={},**kw):
      qualis= {}
      qualis.update(qualifiers)
      qualis.update(kw)
      QtGui.QLineEdit.__init__(self,parent)
      CBaseWidget.__init__(self,dragType=qualis.get('dragType',None))
      self.setCharWidth(qualis.get('charWidth',None))
      self.customContextMenu = qualis.get('customContextMenu',False)
      tip = self.makeToolTip(qualis)
      if len(tip)>0: self.setToolTip(tip)

    '''
    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)
    '''

    
    def mousePressEvent(self,event):
      if event.button() == QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('rightMousePress'),event)
        event.accept()
      else:
        QtGui.QLineEdit.mousePressEvent(self,event)
    

    def contextMenuEvent(self,event):
      if self.customContextMenu:
        self.emit(QtCore.SIGNAL('contextMenuRequest'),event.globalX(),event.globalY())
      else:
        QtGui.QLineEdit.contextMenuEvent(self,event)

    def setValue(self,value=None):
      #print 'CLineEdit.setValue',value
      #import traceback
      #traceback.print_stack(limit=5)
      if value is None or value == 'None':
        self.setText('')
      else:
        self.setText(str(value))

    def getValue(self):
        return str(self.text())

    def editSignal(self):
        return 'editingFinished()'
    

        
class CCheckBox(QtGui.QCheckBox,CBaseWidget):
    def __init__(self,parent=None,qualifiers={},**kw):
      QtGui.QCheckBox.__init__(self,parent)
      qualis={}
      qualis.update(qualifiers)
      qualis.update(kw)
      self.onValue = qualis.get('onValue',True)
      self.offValue = qualis.get('offValue',False)
      tip = self.makeToolTip(qualis)
      if len(tip)>0: self.setToolTip(tip)

    def mousePressEvent(self,event):
      if event.button() == QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('rightMousePress'),event)
        event.accept()
      else:
        QtGui.QCheckBox.mousePressEvent(self,event)

        
    def contextMenuEvent(self,event):
      #print 'CCheckBox.contextMenuEvent'
      self.emit(QtCore.SIGNAL('contextMenuRequest'),event.globalX(),event.globalY())

    def setValue(self,value=None):
        if value is None:
          pass
        elif value == self.onValue:
            self.setChecked(1)
        elif value == self.offValue:
            self.setChecked(0)

    def getValue(self):
        i = int(self.isChecked())
        if i:
            return self.onValue
        else:
            return self.offValue

    def editSignal(self):
        return 'clicked(bool)'

class CCheckBoxUneditable(CCheckBox):
    def mouseReleaseEvent(self,event):
      event.accept()

      
     
class CComboBox(QtGui.QComboBox,CBaseWidget):

    FONTHEIGHT = None

    def __init__(self,parent=None,qualifiers={},**kw):
      qualis = {}
      qualis.update(qualifiers)
      qualis.update(kw)
      QtGui.QComboBox.__init__(self,parent)
      CBaseWidget.__init__(self,dragType=qualis.get('dragType',None))
      values =  qualis.get('enumerators',[])
      if len(values)>0: self.populate(values,qualis.get('menuText',[]))
      self.setEditable(not(qualis.get('onlyEnumerators',False)))
      #print 'CComboBox editable',qualis.get('onlyEnumerators',None),self.isEditable()
      default = qualis.get('default',None)
      #print 'CComboBox default',default
      if default is not None:
        indx = self.findData(QtCore.QVariant(default))
        #print 'CComboBox indx',indx
        if indx>=0: self.setCurrentIndex(indx)
      self.connect(self,QtCore.SIGNAL('editTextChanged(const QString&)'),self.handleEditTextChange)
      self.connect(self,QtCore.SIGNAL('currentIndexChanged(const QString &)'),self.handleMenuChange)
      tip = self.makeToolTip(qualis)
      if len(tip)>0: self.setToolTip(tip)

    def showPopup(self):
      '''
      This is attempt to fix the CDataFileView popup listview being drawn too short after several
      different CDataFileViews have been clicked on.
      Even after we have explicitly set the height of the listview it can appear with not all items
      displayed and the scroll arrows at top/bottom.  This seems to be fixed by setting wrapping True
      but why I dont know.
      This same method is copied in CFinishedJobsCombo
      '''
      QtGui.QComboBox.showPopup(self)
      if sys.platform != 'darwin': return
      listViewList = self.findChildren(QtGui.QListView)
      if len(listViewList)>0:
        listViewList[0].setWrapping(True)
        if CComboBox.FONTHEIGHT is None: CComboBox.FONTHEIGHT = listViewList[0].fontMetrics().height()+2
        listViewList[0].setMinimumHeight(self.count()*CComboBox.FONTHEIGHT+5)
        #print 'CComboBox.showPopup height',self.count(),CComboBox.FONTHEIGHT,listViewList[0].height()
      
    def populate(self,menuItems=[],menuText=[]):
      if menuText is None: menuText = []
      self.menuText = menuText
      self.blockSignals(True)
      self.clear()
      for ii in range(len(menuItems)):
        if ii<len(menuText):
          self.addItem(menuText[ii],QtCore.QVariant(menuItems[ii]))
        else:
          self.addItem(str(menuItems[ii]),QtCore.QVariant(menuItems[ii]))
      self.blockSignals(False)

    def mousePressEvent(self,event):
      if event.button() == QtCore.Qt.RightButton:
        self.emit(QtCore.SIGNAL('rightMousePress'),event)
        event.accept()
      else:
        QtGui.QComboBox.mousePressEvent(self,event)

    def keyPressEvent(self,event):
      #print 'CComboBox.keyPressEvent',event.key()
      if event.key() in [QtCore.Qt.Key_Return,QtCore.Qt.Key_Enter]:
        self.emit(QtCore.SIGNAL('returnPressed'))
        event.accept()
      else:
        QtGui.QComboBox.keyPressEvent(self,event)

    def focusOutEvent(self,event):
      self.emit(QtCore.SIGNAL('focusOut'))
      QtGui.QComboBox.focusOutEvent(self,event)
        
    def contextMenuEvent(self,event):
      #print 'CComboBox.contextMenuEvent'
      self.emit(QtCore.SIGNAL('contextMenuRequest'),event.globalX(),event.globalY())
     
    def setValue(self,value=None):
      
      self.blockSignals(True)
      if value is None:
        self.setEditText('')
        self.setCurrentIndex(-1)
      else:
        ic = self.findText(QtCore.QString(value))
        if ic >= 0:
          text = value
        else:
          ic = self.findData(QtCore.QVariant(value))
          if ic>=0 and ic<len(self.menuText):
            text = self.menuText[ic]
          else:
            text = value
        #print 'CCombo.setValue ic', self.menuText,value,ic,text
        self.setCurrentIndex(ic)
        self.setEditText(str(text))
      self.blockSignals(False)


    def getValue(self):
     variant = self.itemData(self.currentIndex())
     #print 'CComboBox.getValue',variant.isNull(),self.currentText()
     baseType = ''
     if not variant.isNull():
       if baseType == 'int':
         value = variant.toInt()
       elif baseType == 'float':
         value = variant.toFloat()
       else:
         value = str(variant.toString())
       return value
     else:
       return str(self.currentText())

    def editSignal(self):
         return 'currentIndexChanged(int)'

    def wheelEvent(self,event):
      # Intercept wheel event 'cos mostly gets input intended to scroll
      # a frame
      # 'CComboBox.wheelEvent'
      event.ignore()

    '''
    def mouseReleaseEvent(self,event):
      #print 'CCombo.mouseReleaseEvent',event.x(),event.y()
      QtGui.QComboBox.mouseReleaseEvent(self,event)
    '''

    
    def handleMenuChange(self,item):
      '''
      self.blockSignals(True)
      variantObj=self.itemData(item)
      #print 'CComboBox.handleMenuChange',item,variantObj
      if variantObj is not None:
        data = variantObj.toString()
        #print 'CComboBox.handleMenuChange',item,data
        if data is not None: self.setEditText(str(data))
      self.blockSignals(False)
      '''
      #print 'CCombo.handleMenuChange',item
      self.emit(QtCore.SIGNAL('dataChanged'))
      
      
    def handleEditTextChange(self,txt):
     
      self.blockSignals(True)
      text = self.currentText()
      #print 'CComboBox.handleLineEdit',text
      if len(text)>0:
        idx = self.findText(text)
        if idx<0:
          idx = self.findData(QtCore.QVariant(text))
        self.setCurrentIndex(idx)
      else:
        self.setCurrentIndex(-1)
      self.blockSignals(False)
      

class CTextEdit(QtGui.QTextEdit,CBaseWidget):

  def __init__(self,parent=None,qualifiers={},**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)   
    QtGui.QTextEdit.__init__(self,parent)
    CBaseWidget.__init__(self,dragType=qualis.get('dragType',None))
    if not qualis.get('editable',True): self.setReadOnly(True)
    tip = self.makeToolTip(qualis)
    if len(tip)>0: self.setToolTip(tip)
    import CCP4TaskWidget
    

  def editSignal(self):
    return 'textChanged()'

  def setValue(self,value=None):     
    self.blockSignals(True)
    if value is None:
      self.document().clear()
    else:
      self.document().setPlainText(value)
    self.blockSignals(False)

  def getValue(self):
    text = str(self.toPlainText())
    if len(text) == 0:
      return None
    else:
      return text

  def focusOutEvent(self,event):
    print 'CTextEdit.focusOutEvent'
    self.emit(QtCore.SIGNAL('textChanged()'))
    QtGui.QTextEdit.focusOutEvent(self,event)
    

class CRadioButtonGroup(QtGui.QButtonGroup,CBaseWidget):

  def __init__(self,parent=None,qualifiers={},**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)   
    QtGui.QButtonGroup.__init__(self,parent)
    self.setExclusive(True)
    CBaseWidget.__init__(self,dragType=qualis.get('dragType',None))
    self.toolTip = self.makeToolTip(qualis)
    #self.buttonLookup = {}
    self.lastButtonId = -1

  def addRadioButton(self,value=None,text=None):     
    if text is not None and text is not NotImplemented:
      but = QtGui.QRadioButton(text,self.parent())
    else:
      but = QtGui.QRadioButton(self.parent())
    but.setToolTip(self.toolTip)
    but.setObjectName(str(value))
    self.lastButtonId = self.lastButtonId + 1
    #self.buttonLookup[value] = self.lastButtonId
    QtGui.QButtonGroup.addButton(self,but,self.lastButtonId)
    return but

  def removeRadioButton(self,value=None):
    for but in self.buttons():
      if str(but.objectName()) == value:
        QtGui.QButtonGroup.removeButton(self,but)
        return

  def setValue(self,value=None):
    #print 'CRadioButtonGroup.setValue',value,self.buttons()
    value = str(value)
    for but in self.buttons():
      if str(but.objectName()) == value:
        self.blockSignals(True)
        but.setChecked(True)      
        self.blockSignals(False)
        return

  def getValue(self):
    indx = int(self.checkedId())
    v = str(self.button(indx).objectName())
    #print 'CRadioButtonGroup.getValue',indx,v
    return v

  def getButton(self,value):
    #print 'CRadioButtonGroup.getButton',value,type(value)
    if isinstance(value,CCP4Data.CData): value = value.get()
    for but in self.buttons():
      if str(but.objectName()) == value:
        return but
    return None

  def editSignal(self):
    return 'buttonReleased(int)'

  def setToolTip(self,tip):
    for but in self.buttons(): but.setToolTip(tip)
    

class CBooleanRadioButtonGroup(CRadioButtonGroup):

  def __init__(self,parent=None,qualifiers={},**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CRadioButtonGroup.__init__(self,parent=parent,qualifiers=qualis)
    menuText = qualis.get('menuText',[NotImplemented,NotImplemented])
    self.addRadioButton(True,menuText[0])
    self.addRadioButton(False,menuText[1])


class CFolder(QtGui.QFrame):

   MARGIN = 4

   def __init__(self,parent=None,title='Folder',open=1,titleBar=1,toggle=[],toggleFunction=[],icon=None):
       QtGui.QFrame.__init__(self,parent)
       self.setObjectName(title)
       layout = QtGui.QVBoxLayout()
       layout.setSpacing(CFolder.MARGIN)
       layout.setContentsMargins(CFolder.MARGIN,CFolder.MARGIN,CFolder.MARGIN,CFolder.MARGIN)
       if titleBar:
         self.titleBar = CClickableFrame(self)
         self.titleBar.setFrameStyle(QtGui.QFrame.Box|QtGui.QFrame.Plain)
         layout.addWidget(self.titleBar)
         title_layout = QtGui.QHBoxLayout()
         title_layout.setSpacing(CFolder.MARGIN)
         title_layout.setContentsMargins(CFolder.MARGIN,CFolder.MARGIN,CFolder.MARGIN,CFolder.MARGIN)
         if icon is not None:
           pix = PIXMAPMANAGER().getPixmap(icon)
           if pix is not None:
             ico = QtGui.QIcon(pix)
             self.titleBar.layout().addWidget(ico,0,QtCore.Qt.AlignRight)

         self.titleIcon = QtGui.QLabel(self)
         title_layout.addWidget(self.titleIcon)
         self.titleLabel = QtGui.QLabel(title,self)
         title_layout.addWidget(self.titleLabel)
         title_layout.addStretch(5)
         self.titleBar.setLayout(title_layout)
       else:
         self.titleBar = None
         self.titleIcon = None

       self.contents = QtGui.QFrame(self)
       self.contentsLayout = None
       layout.addWidget(self.contents)
       #layout.addStretch(5)
       if not open: open = 0
       self.open = 1-open
       self.toggleFolder()
         
       self.setLayout(layout)

       if self.titleBar is not None:
         self.connect(self.titleBar,QtCore.SIGNAL('mouseRelease'),self.toggleFolder)

       if len(toggle)>0:
         if len(toggle) == 1:
           self.parent().setToggle(self,toggle[0],'open',[True])
         elif len(toggle) == 2:
           self.parent().setToggle(self,toggle[0],toggle[1],[True])
         else:
           self.parent().setToggle(self,toggle[0],toggle[1],toggle[2])
       if len(toggleFunction):
         self.parent().setToggle(self,toggleFunction[0],toggleFunction=toggleFunction[1],parameterList=toggleFunction[2])
       

   def title(self):
     return self.titleLabel.text().__str__()
   
   def setTitleColour(self,colour):
     if self.titleBar:
       self.titleBar.setStyleSheet("QFrame { background-color:"+colour +"; }")


   def setContentsLayout(self,layout):
     self.contentsLayout = layout
     self.contents.setLayout(layout)

   def toggleFolder(self):
     self.open = 1 - self.open
     if self.open:
        self.contents.show()
        self.resetIcon('toc-minus')
     else:
        self.contents.hide()
        self.resetIcon('toc-plus')
     #print 'CFolder.toggleFolder',self,self.open
     self.emit(QtCore.SIGNAL('folderToggled'))
        
   def resetIcon(self,feature):
     if not self.titleIcon: return
     pix = QtGui.QPixmap(PIXMAPMANAGER().getPixmap(feature))
     self.titleIcon.setPixmap(pix)
      
   def openFolder(self):
     if not self.open: self.toggleFolder()

   def closeFolder(self):
     if self.open: self.toggleFolder()

   def isOpen(self):
     return self.open

     
class CIconButton(QtGui.QToolButton,CBaseWidget):
  '''Sub-class QToolButton for drag'n'drop'''
  def __init__(self,parent=None,icon=None,dragType=None):
    QtGui.QToolButton.__init__(self,parent)
    CBaseWidget.__init__(self,dragType=dragType)
    self.setPopupMode(QtGui.QToolButton.InstantPopup)
    # The menu indicator on Linux is making the icon small
    # The flollowing ought to remove menu indicator but does
    # not due to bug that is worked round by the reimplementation
    # of paintEvent() below
    self.setStyleSheet("QToolButton::menu-indicator { image: none; }" )
    self.setToolTip('Left mouse to drag, right mouse for menu')
    if icon is not None: self.setIcon(icon)
    self.dragStartPosition = None

  # From https://bugreports.qt-project.org/browse/QTBUG-2036
  def paintEvent(self,event):
    p = QtGui.QStylePainter(self)
    opt = QtGui.QStyleOptionToolButton()
    self.initStyleOption(opt)
    p.drawComplexControl(QtGui.QStyle.CC_ToolButton,opt)

  def sizeHint(self):
    return QtCore.QSize(ICONBUTTONSIZE,ICONBUTTONSIZE)

      
  def mousePressEvent(self,event):
        #print 'CIconButton.mousePressEvent',event.button(),self.dragType()
        if event.button() == QtCore.Qt.LeftButton:
          self.emit(QtCore.SIGNAL('leftMousePress'),event)
          if self.dragType() is not None:
            self.dragStartPosition = event.pos()
            event.accept()
        elif event.button() == QtCore.Qt.RightButton:
            self.emit(QtCore.SIGNAL('rightMousePress'),event)
            QtGui.QToolButton.mousePressEvent(self,event)
        return
      
  def mouseReleaseEvent(self,event):
        #print 'CIconButton.mouseReleaseEvent'
        if event.button() == QtCore.Qt.RightButton:
            self.emit(QtCore.SIGNAL('rightMouseRelease'),event)
        if event.button() == QtCore.Qt.LeftButton:
            self.emit(QtCore.SIGNAL('leftMouseRelease'),event)
        QtGui.QToolButton.mouseReleaseEvent(self,event)

  def mouseMoveEvent(self,event):
        #print 'CIconButton.mouseMoveEvent'
        #if event.button() != QtCore.Qt.LeftButton:
        #    print 'wrong button',event.button(),QtCore.Qt.LeftButton
        #   return
        if self.dragType() is None: return
        if self.dragStartPosition is not None and \
          (event.pos() - self.dragStartPosition).manhattanLength() < QTAPPLICATION().startDragDistance():
            return
        self.parent().startDrag()
   

class CViewWidget(QtGui.QFrame,CBaseWidget):

    MODEL_CLASS = CCP4Data.CData
    MARGIN = 4
    STRETCH = -1

    def __init__(self,parent=None,qualifiers={},**kw):
      QtGui.QWidget.__init__(self,parent)
      CBaseWidget.__init__(self)
      self.editable = qualifiers.get('editable',True)
      self._presetWidth = qualifiers.get('width',-1)
      self.model=None
      self.widgets = {}
      self.lastWarning = None
      self.isValid = None
      self.blockUpdateView = False
      self.validityMessage = None

    def enterEvent(self,event):
      if self.model is None or not self.editable: return
      #print 'enterEvent',self.model.objectName()
      try:
        self.parentTaskWidget().setMessage(self.validityMessage,self.model.objectPath())
      except:
        pass

    def leaveEvent(self,event):
      if self.model is None or not self.editable: return
      #print 'leaveEvent',self.model.objectName()
      try:
        self.parentTaskWidget().unsetMessage(self.model.objectPath())
      except:
        pass
      
    def editSignal(self):
      return 'clicked(bool)'

    def setValue(self,value=None):
      if self.blockUpdateView: return
      if hasattr(self,'widget'):
        self.widget.blockSignals(True)
        self.widget.setValue(value)
        self.widget.blockSignals(False)
      else:
        if value is None: return
        for item in value.keys():      
          w = self.widgets.get(item,None)
          if w is not None:
            w.blockSignals(True)
            w.setValue(value[item])
            w.blockSignals(False)
      self.lastWarning = None

    def getValue(self):
      if  isinstance(self.model,CCP4Data.CBaseData):
        data = self.widget.getValue()
      else:
        data = {}
        for item in self.model.dataOrder():
          w = self.widgets.get(item,None)
          if w is not None:
            data[item] = w.getValue()
      return data

    def getModel(self):
      return self.model
  
    def setModel(self,model):
      if isinstance(model,self.MODEL_CLASS) or model is None:
        if self.model is not None:
            self.connectUpdateViewFromModel(False)
        self.model = model
        #self.updateViewFromModel()
        if model is not None: self.connectUpdateViewFromModel(True)
        self.validate()

    def modelObjectPath(self):
      if self.model is None:
        return ''
      else:
        return self.model.objectPath()

    def reset(self):
      if not isinstance(self.model,CCP4Data.CBaseData):
        for w in self.widgets.keys(): self.widgets[w].reset()


    def connectUpdateViewFromModel(self,mode=True,propagate=False):
      if self.model is None: return      
      self.blockUpdateView = not(mode)
      #self.model.blockSignals(not(mode))
      if mode:
        self.connect(self.model,QtCore.SIGNAL('dataChanged'),self.updateViewFromModel)
      else:        
        self.disconnect(self.model,QtCore.SIGNAL('dataChanged'),self.updateViewFromModel)
      if propagate:
        p = self.parent()
        #print 'CViewWidget.updateModelFromView parent',p
        while not isinstance(p,(QtGui.QDialog,QtGui.QMainWindow)):
          if isinstance(p,CViewWidget):
            try:
                p.connectUpdateViewFromModel(mode)
                #print 'discon',p
            except:
                p = None
            else:
                p = p.parent()
          else:
              p = p.parent()

    def unsetModel(self):
      if self.model is not None:
          # ARGHHH - how to avoid disconnect (besides not changing the model)?
          self.connectUpdateViewFromModel(False)
          self.model = None

    def updateViewFromModel(self):
      if self.model is None: return
      if self.blockUpdateView: return
      #print 'CViewWidget.updateViewFromModel',self.model.objectName(),self.model.get0()
      #  import traceback
      #  traceback.print_stack(limit=6)
      self.blockSignals(True)
      self.setValue(self.model.get0())
      self.blockSignals(False)
      self.lastWarning = None

    def updateModelFromView(self):
      if self.model is None: return
      self.connectUpdateViewFromModel(False)
      isValid = None
      try:
        self.model.set(self.getValue())
      except:
        isValid = False
      self.connectUpdateViewFromModel(True)
      '''
      if self.lastWarning is not None:
        if data == self.lastWarning:
          return
        else:
          self.lastWarning = None
      '''
      #print 'CViewWidget.updateModelFromView',self.model.objectName(),self.model,self.getValue(),isValid
      self.validate(isValid=isValid)

    def updateModelFromText(self):
      pass

    def stretchFactor(self):
      return self.STRETCH

    def validate(self,isValid=None,excludeWidgets=[],report=None,reportMessage=True):
      if self.model is None: return False
      #print 'CViewWidget.validate',self.model.objectName(),isValid,report
      if not getattr(self,'editable',True): return True
      #print 'CViewWidget.validate',self.model.objectName(),type(self.model),isValid,self.model.get(),self.widgets
      
      if isValid is None:
        v = self.model.validity(self.model.get())
        update = False
        #print 'CViewWidget.validate maxSeverity',v.maxSeverity(),v.report(ifStack=False,mode=2)
        if v.maxSeverity()>SEVERITY_WARNING:
          if self.isValid is None or self.isValid: update = True
          self.setProperty("isValid",False)
          self.setProperty("hasWarning",False)
          self.isValid = False
          self.validityMessage = v.report(ifStack=False,user=True,mode=2)
        elif v.maxSeverity()==SEVERITY_WARNING:
          # For warning message do not highlight widget but do set the warning text
          if self.isValid is None or not self.isValid: update = True
          self.setProperty("isValid",True)
          self.setProperty("hasWarning",True)
          self.isValid = True
          self.validityMessage = v.report(ifStack=False,user=True,mode=2)
        else:
          if self.isValid is None or not self.isValid: update = True
          self.setProperty("isValid",True)
          self.setProperty("hasWarning",False)
          self.isValid = True
          self.validityMessage = None
      else:
        update = True
        self.setProperty("isValid",isValid)
        self.isValid = isValid
        self.validityMessage = report
      #print 'CViewWidget.validate',self.model.objectName(),self.isValid,self.validityMessage,reportMessage
      # It does not update correctly unless we do this --
      # See http://stackoverflow.com/questions/1595476/are-qts-stylesheets-really-handling-dynamic-properties
      if update:
        self.updateValidityIndicator()
        try:
          self.parentTaskWidget().updateMessage(report,self.model.objectPath())
        except:
          pass
      if isinstance(self.widgets,dict):
        for key,w in self.widgets.items():
          if key not in excludeWidgets:
            w.validate(isValid,reportMessage=False)
      else:
        for w in self.widgets:
          w.validate(isValid,reportMessage=False)

      '''
      if self.editable and self.isVisible() and report is not None:
        geom = self.geometry()
        globalPos = self.mapToGlobal(QtCore.QPoint(geom.x(),geom.y()))
        self.message = CFramelessWarning(parent=self,message=report,position=globalPos)
        self.setFocus(QtCore.Qt.PopupFocusReason)
      '''
  
      return self.isValid


    def updateValidityIndicator(self):
      self.style().unpolish(self)
      self.style().polish(self)
      self.update()

    def setFocus1(self,reason,indx):
      #print 'CViewWidget.setFocus1',self,self.model.objectName(),reason,indx
      self.setFocus(reason)

    def setToolTip(self,tip):
      #print 'CViewWidget.setToolTip',self.model.objectName(),tip,self.toolTip()
      #QtGui.QFrame.setToolTip(self,tip+'\n'+self.toolTip())
      if getattr(self,'widget',None) is not None:
        try:
          self.widget.setToolTip(tip+'\n'+self.widget.toolTip())
        except:
          pass
      try:
        for w in self.widgets.values():
          w.setToolTip(tip+'\n'+w.toolTip())
        for l in self.findChildren(QtGui.QLabel):
          l.setToolTip(tip)
      except:
        pass

      
    
class CSeparator(CViewWidget):
  '''Hold container widgets in auto-generated gui'''

  import CCP4Container

  MODEL_CLASS = CCP4Container.CContainer

  def __init__(self,parent=None,qualifiers={},**kw):
    CViewWidget.__init__(self,parent,)
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    
    layout = QtGui.QHBoxLayout()
    layout.setSpacing(CViewWidget.MARGIN)
    layout.setContentsMargins(CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN)
    self.setLayout(layout)

    self.label = CLabel(self)
    self.label.setStyleSheet("QFrame { background-color:"+CONTRASTCOLOUR +"; }")
    layout.addWidget(self.label)

  def updateViewFromModel(self):
    if self.model is not None:
      self.label.setValue(str(self.model.objectName()))
    
class CComplexLineWidget(CViewWidget):
  MARGIN = 4
  MODEL_CLASS = CCP4Data.CData
  TABLE_EDIT_MODE = 'window'
  
  def __init__(self,parent=None,qualifiers={}):
    qualis = {}
    qualis.update(qualifiers)
    self.iconButton = None
    self.iconMenu = None
    self.widgets = {}
    CViewWidget.__init__(self,parent,qualis)
    #print 'CComplexLineWidget.__init__',self.MODEL_CLASS, qualis.get('stack',False)
    self._stacked = qualis.get('stacked',False)
    self._dragType = qualis.get('dragType',None)
    self._dropTypes = qualis.get('dropTypes',[])
    if self._dragType is None:
      ty = (self.MODEL_CLASS.__name__).split('.')[-1]
      if ty[0]=='C':
        self._dragType= ty[1:]
      else:
        self._dragType= ty
    self.iconName = qualis.get('iconName',None)
    
      
    if qualis.get('gridLayout',False):
      layout = QtGui.QGridLayout()
    elif  qualis.get('vboxLayout',False):
      layout = QtGui.QVBoxLayout()
    else:
      layout = QtGui.QHBoxLayout()
    layout.setSpacing(CComplexLineWidget.MARGIN)
    layout.setContentsMargins(CComplexLineWidget.MARGIN,CComplexLineWidget.MARGIN,CComplexLineWidget.MARGIN,CComplexLineWidget.MARGIN)
    self.setLayout(layout)
    

    iconButton = qualis.get('iconButton',None)
    if iconButton is None: iconButton = (parent is not None and (not isinstance(parent,CComplexLineWidget) or isinstance(parent,CListView)))
    if iconButton:
      self.createIconButton()
      '''
      if qualis.get('guiLabel',NotImplemented) is not NotImplemented:
        layout.addWidget(QtGui.QLabel(qualis['guiLabel'],self))
      '''
      self.setFrameShape(QtGui.QFrame.StyledPanel)
    
      self.connect(self.iconButton,QtCore.SIGNAL('rightMousePress'),self.updateMenu)
      if self.editable:
        self.connect(self.iconButton,QtCore.SIGNAL('acceptDropData'),self.acceptDropData)
        self.connect(self,QtCore.SIGNAL('acceptDropData'),self.acceptDropData)

   
  def createIconButton(self):
    if self.iconName is None and self.dragType() is not None:
      self.iconName = self.dragType()
    #print 'CComplexLineWidget.createIconButton',self,type(self),self.dragType(),'iconName',self.iconName
    import CCP4GuiUtils
    icon =CCP4GuiUtils.createIcon(name=self.iconName)    
    self.iconButton = CIconButton(self,icon=icon,dragType= self._dragType)
    self.iconButton.setIcon(icon)
    layout = self.layout()
    if isinstance(layout,QtGui.QGridLayout):
      layout.addWidget(self.iconButton,0,0)
    else:
      layout.addWidget(self.iconButton)

    self.iconMenu = QtGui.QMenu()
    self.iconButton.setMenu(self.iconMenu)
    height = self.iconButton.height()
    self.iconButton.setIconSize(QtCore.QSize(height,height))
    #print 'createIconButton',self.iconButton.iconSize().height(),self.iconButton.height()

  def createListButtons(self,editor= False):
    buttonBox = QtGui.QFrame()
    if CListView.SIDE_BOX:
      buttonBox.setLayout(QtGui.QVBoxLayout())
    else:
      buttonBox.setLayout(QtGui.QHBoxLayout())
    buttonBox.layout().setSpacing(0)
    buttonBox.layout().setContentsMargins(0,0,0,0)
    #buttonBox.layout().addStretch(1)
    menu =  [['list_add_grey','Add item','append'],['list_delete_grey','Remove item','delete']]
    if editor: menu.append(['bullet_arrow_right','Open editor','editor'])
    for item in menu:
      icon = QtGui.QIcon(PIXMAPMANAGER().getPixmap(item[0]))
      button = QtGui.QToolButton(self)
      button.setIcon(icon)
      button.setMaximumHeight(ICONBUTTONSIZE)
      button.setMaximumWidth(ICONBUTTONSIZE)
      #button.setText(item[1])
      button.setToolTip(item[1])
      buttonBox.layout().addWidget(button)
      self.connect(button,QtCore.SIGNAL('clicked()'),functools.partial(self.handleButtonClick,item[2]))
    if not CListView.SIDE_BOX: buttonBox.layout().addStretch(1)
    if editor: buttonBox.layout().itemAt(2).widget().setCheckable(True)
    
    return buttonBox

  def handleButtonClick(self,item):
    pass

  def startDrag(self):
    mimeData = self.createMimeData()
    #print 'CComplexLineWidget.startDrag',mimeData,mimeData is None
    if mimeData is None: return
    drag = QtGui.QDrag(self)
    drag.setMimeData(mimeData)
    drag.setPixmap(self.iconButton.icon().pixmap(DRAGICONSIZE,DRAGICONSIZE))
    self.connect(drag,QtCore.SIGNAL('targetChanged(QWidget*)'),self.changedTarget)
    drag.exec_(QtCore.Qt.CopyAction)

  def changedTarget(self,widget):
    #print 'CComplexLineWidget.changedTarget',widget
    pass
      
  def createMimeData(self):
    dragText = self.dragData()
    #print 'CComplexLineWidget.createMimeData',repr(self),self.dragType(),dragText
    if dragText is None: return None
    data = QtCore.QByteArray()
    data.append(dragText)
    mimeData = QtCore.QMimeData()
    # With mime type as text the data can be dropped on desktop
    # but the type of the data is lost
    #mimeData.setData('text/plain',data)
    mimeData.setData(self.dragType(),data)
    return mimeData

  def isWidgetOpen(self):
    return True

  def hideWidgetLabel(self):
    if self.isWidgetOpen():
      return 'Hide'
    else:
      return 'Show'

  def handleHideWidget(self):
    pass
  
  def getActionDef(self,name):
    
    def e(): return self.dragType() is not None
    if name == 'copy':
      return dict (
        text = self.tr("Copy"),
        tip = self.tr('Copy data'),
        enabled = e,
        slot = self.copy
      )
    def e(): return (self.editable and self.clipboardLoaded())
    if name == 'paste':
      return dict (
        text = self.tr("Paste"),
        tip = self.tr('Paste data'),
        enabled = e,
        slot = self.paste
      )
    def e(): return (self.model.qualifiers('helpFile') is not NotImplemented )
    if name == 'help':
      return dict (
        text = self.tr("Help"),
        tip = self.tr('Help'),
        slot = self.help,
        enabled = e,
      )
    if name == 'clear':
      return dict (
        text = self.tr("Clear"),
        tip = self.tr('Clear all data'),
        slot = self.clear,
        enabled = self.editable
      )
    def e(): return (self.model is not None and hasattr(self.model,'exists') and self.model.exists())
    if name[0:4] == 'view':
      if name == 'view':
        text = 'View'
      elif name == 'view_text':
        text = 'View as text'
      else:
        text = "View in "+name[5:]
      return dict (
        text = self.tr(text),
        tip = self.tr('View data'),
        slot = functools.partial(self.openViewer,name),
        enabled = e
      )
    if name == 'handleStack':
      return dict (
        text = self.tr("Alternate view"),
        tip = self.tr('View data'),
        slot = self.parent().handleStack,
        checkable = True,
        checked = self.parent().isAltViewOpen
      )
    if name == 'hide':
      return dict (
        text = self.hideWidgetLabel,
        tip = self.tr('Open/close the widget'),
        slot = self.handleHideWidget
      )
    if name == 'show_list':
      return dict (
        text = self.tr("Show list"),
        tip = self.tr('Enter multiple data items'),
        slot = self.setListVisible,
        enabled = self.showListEnabled,
        checkable = True,
        checked = self.isListVisible
      )

  def getMenuDef(self):
    if self.editable:
      menu = ['clear','copy','paste','help']
    else:
      menu = ['copy','help']
    if self._stacked: menu.insert(0,'handleStack')
    return menu
    
  def updateMenu(self,event=None):
    #print 'CDataFileView.updateMenu',self,type(self),self.getMenuDef()
    import CCP4GuiUtils
    self.iconMenu.clear()
    CCP4GuiUtils.populateMenu(self,menuWidget=self.iconMenu,definition=self.getMenuDef(),getActionDef=self.getActionDef)
    self.iconMenu.popup(event.globalPos())


  def copy(self):
    #print 'CComplexLineWidget.copy',self.dragType()
    if self.dragType() is None: return
    mimeData = self.createMimeData()
    if mimeData is not None:
      cb = QTAPPLICATION().clipboard()
      cb.setMimeData(mimeData)


  def paste(self):
    #print 'CComplexLineWidget.paste'
    if self.dragType() is None: return 0
    mimeData = QTAPPLICATION().clipboard().mimeData()
    textData = self.acceptMimeData(mimeData)
    #print 'CComplexLineWidget.paste textData',textData
    if textData is not None:
      self.acceptDropData(textData)

  def clipboardLoaded(self):
    if self.dragType() is None: return 0
    mimeData = QTAPPLICATION().clipboard().mimeData()
    textData = self.acceptMimeData(mimeData)
    if textData is not None:
      return 1
    else:
      return 0

  def help(self):
    helpFile = self.model.qualifiers('helpFile')
    if helpFile is not NotImplemented:
      WEBBROWSER(mini=True).loadWebPage(helpFileName=helpFile)
    else:
      print 'No help file defined for ',self.model.__class__

  def dragData(self):
    #print 'CComplexLineWidget.dragData',self.model.xmlText(pretty_print=False)
    return self.model.xmlText(pretty_print=False)

  def acceptDropData(self,textData):
    #print 'CComplexLineWidget.acceptDropData',textData
    from lxml import etree
    tree = etree.fromstring(textData)
    tree.tag = self.dragType()
    #print 'CComplexLineWidget.acceptDropData',textData,tree.tag
    self.connectUpdateViewFromModel(False)
    self.model.unSet()
    self.model.setEtree(tree)
    #print 'CComplexLineWidget.acceptDropData model',self.model
    self.connectUpdateViewFromModel(True)
    self.updateViewFromModel()
    #print 'CComplexLineWidget.acceptDropData validity',self.model.validity(self.model.get()).report()
    self.validate()

  def openViewer(self,**kw):
    print 'CComplexLineWidget.openViewer should by reimplemented in subclass'

  def clear(self):
    self.model.blockSignals(True)
    self.model.unSet()
    self.model.blockSignals(False)
    #print 'CComplexLineWidget.clear',self.model.objectName(),self.model
    self.updateViewFromModel()
    self.validate()


  def setModel(self,model):
    #print 'CComplexLineWidget.setModel',model,self.widgets.keys()
    #if model is not None: print model.objectName()
    if model is None or isinstance(model,self.MODEL_CLASS):
      CViewWidget.setModel(self,model)

      if model is not None:
        self.connect(model,QtCore.SIGNAL('dataChanged'),self.validate)
        toolTip = model.qualifiers('toolTip')
        #SJM 07/07/2017 Commented this out because it triggers https://fg.oisin.rc-harwell.ac.uk/tracker/?func=detail&group_id=92&aid=1266&atid=118
        """
        if toolTip is not NotImplemented and toolTip is not None  and self.iconButton is not None:
          self.iconButton.setToolTip(toolTip+'\n'+self.iconButton.toolTip())
        """
        #print 'CComplexLineWidget.setModel', self.widgets.items()
        for key,w in self.widgets.items():
          #print 'CComplexLineWidget.setModel',key,w
          if isinstance(w,CViewWidget): w.setModel(model.get(key))
      else:
        for key,w in self.widgets.items():
           if isinstance(w,CViewWidget): w.setModel(None)

  def getModel(self):    
    return self.model

  def setToolTip(self,toolTip):
    try:
      self.iconButton.setToolTip(toolTip+'\n'+self.iconButton.toolTip())
    except:
      pass
    CViewWidget.setToolTip(self,toolTip)
                      
class CDataFileView(CComplexLineWidget):

  STRETCH = 5  
  MODEL_CLASS = CCP4File.CDataFile
  ERROR_CODES = { 100 : { 'description' : 'Error copying file' },
                 101 : { 'description' : 'Unable to access ProjectManager to set project combo box' }                 
                 }
  MAXCHARS = 9999

  def __init__(self,parent=None,model=None,qualifiers={},**kw):
    
    qualis = qualifiers
    
    CComplexLineWidget.__init__(self,parent,qualifiers=qualis)
    self.setAcceptDrops(1)
    self.ifJobCombo = qualis.get('jobCombo',True)
    self.ifInfo = qualis.get('ifInfo',False)
    self.autoInfoOnFileImport=qualis.get('autoInfoOnFileImport',True)
    toolTip = qualis.get('toolTip',NotImplemented)
    self.browser = None
    self.databaseBrowser= None
    self.jobChooser = None
    self.model = None
    self.role = qualifiers.get('role',None)

    if qualis.get('vboxLayout',False):
      layout = QtGui.QHBoxLayout()
      iconWidgetItem = self.layout().takeAt(0)
      if iconWidgetItem is not None:
        layout.addWidget(iconWidgetItem.widget())
        self.layout().addLayout(layout)
    else:
      layout = self.layout()

    #print 'CDataFileView.init qualifiers',qualifiers
    if qualifiers.get('stackButton',None) is not None: layout.addWidget(qualifiers['stackButton'])

    if qualis.get('guiLabel',None) not in (None,NotImplemented):
      label = QtGui.QLabel(qualis['guiLabel'],self)
      label.setMinimumWidth(100)
      layout.addWidget(label)

    if self.editable:
      if self.ifJobCombo:
        self.nComboFiles = 0
        self.jobCombo = CComboBox(self,qualis,onlyEnumerators=True)
        self.jobCombo.setToolTip('Select a file output by previous job from menu')
        #self.jobCombo.setStyleSheet("QComboBox { combobox-popup: 0; }");
        #self.jobCombo.setMaxVisibleItems(20)
        self.jobCombo.setEditable(False)
        # http://stackoverflow.com/questions/11252299/pyqt-qcombobox-setting-number-of-visible-items-in-dropdown
        #self.jobCombo.setStyleSheet("QComboBox { combobox-popup: 0; }")
        self.jobCombo.setMaxVisibleItems(10)
        #if toolTip is not NotImplemented: self.jobCombo.setToolTip(toolTip)
        layout.addWidget(self.jobCombo,5)
        self.setFocusProxy(self.jobCombo)
      else:       
        self.fileLineEdit = CLineEdit(self,qualifiers=qualis)
        self.fileLineEdit.setToolTip('Name of file or full path for file not associated with project')
        if toolTip is not NotImplemented: self.fileLineEdit.setToolTip(toolTip)
        layout.addWidget(self.fileLineEdit,5)
        self.setFocusProxy(self.fileLineEdit)
    else:
      if self.ifJobCombo:
        self.jobLabel = CLabel(self,qualifiers=qualis,editable=False)
        self.jobLabel.setToolTip('Using the best file available at finish of this job')
        layout.addWidget(self.jobLabel,5)
        self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('fileUpdated'),self.handleDbFileUpdated)
      else:
        self.fileLineEdit = CLabel(self,qualifiers=qualis,editable=False)
        layout.addWidget(self.fileLineEdit,5)


    if self.editable:
      #print  'browseDb',qualis.get('browseDb',None)
      if qualis.get('browseDb',None) is not None:
        browseDb = qualis['browseDb']
      elif model is None:
        browseDb=True
      else:
        browseDb = model.qualifiers('fromPreviousJob')

      ifDownload = PREFERENCES().NATIVEFILEBROWSER and model is not None and model.qualifiers('downloadModes') is not NotImplemented
       

      iconDefn =  [ [ ifDownload , 'download', 'Download file' , self.downloadGui ],
                    [ True , 'file_manager', 'Browse files', self.openBrowser],
                    [ browseDb, 'database' ,'Browse database', self.openDatabaseBrowser ] ]
     
      
      for draw,iconName,toolTip,action in iconDefn:
        if draw:
          icon = QtGui.QIcon(PIXMAPMANAGER().getPixmap(iconName))
          browserButton = QtGui.QToolButton(self)
          browserButton.setIcon(icon)
          browserButton.setMaximumHeight(ICONBUTTONSIZE)
          browserButton.setMaximumWidth(ICONBUTTONSIZE)
          browserButton.setToolTip(toolTip)
          layout.addWidget(browserButton)
          self.connect(browserButton,QtCore.SIGNAL('clicked()'),action)
        
      if not self.ifJobCombo:
        self.connect(self.fileLineEdit,QtCore.SIGNAL('acceptDropData'),self.acceptDropData)
        self.connect(self.fileLineEdit,QtCore.SIGNAL('editingFinished()'),self.updateModelFromView)
        self.connect(self.fileLineEdit,QtCore.SIGNAL('textEdited(const QString &)'),self.updateModelFromView1)

    self.setModel(model)
    self.validate()

    if self.editable and self.ifJobCombo:
      self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.handleJobFinished)
      self.connect(self.jobCombo,QtCore.SIGNAL('acceptDropData'),self.acceptDropData)
      self.connect(self.jobCombo,QtCore.SIGNAL('currentIndexChanged(int)'),self.handleJobComboChange)
      
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('fileUpdated'),self.handleFileUpdated)
    if self.parentTaskWidget() is not None:
      self.connect(self.parentTaskWidget(),QtCore.SIGNAL('followFromJobUpdated'),self.handleFollowFrom)

  def downloadGui(self):
    '''Display a window to download from web server'''
    import os
    import CCP4FileBrowser
    projectId = self.parentTaskWidget().projectId()
    downloadDir = os.path.join(PROJECTSMANAGER().db().getProjectDirectory(projectId),'CCP4_DOWNLOAD')
    self.dowloadDialog = QtGui.QDialog(self)
    self.dowloadDialog.setLayout(QtGui.QVBoxLayout())
    self.dowloadDialog.setWindowTitle('Download file from database')
    self.dowloadDialog.layout().addWidget(QtGui.QLabel('Downloaded file will be saved to '+downloadDir+' directory',self))
    dialog1 = CCP4FileBrowser.CFileDialog1(self.dowloadDialog,False,True)
    self.dowloadDialog.layout().addWidget(dialog1)
    dialog1.drawDownload(self.model.qualifiers('downloadModes'),projectId=projectId)
    self.dowloadDialog.show()
    self.connect(dialog1,QtCore.SIGNAL('selectDownloadedFile'),self.handleDownloadFinished)
    
  def handleDownloadFinished(self,fileName,downloadInfo):
    #print 'CDataFileView.handleDownloadFinished',fileName,downloadInfo
    self.dowloadDialog.close()
    self.dowloadDialog.deleteLater()
    del self.dowloadDialog
    self.handleBrowserOpenFile(fileName,downloadInfo)
    
    
  def setToolTip(self,toolTip):
    try:
      self.iconButton.setToolTip(toolTip+'\n'+self.iconButton.toolTip())
    except:
      pass
    if getattr(self,'jobCombo',None) is not None:
      self.jobCombo.setToolTip(toolTip+'\n'+self.jobCombo.toolTip())
    elif getattr(self,'fileLineEdit',None) is not None:
      self.fileLineEdit.setToolTip(toolTip+'\n'+self.fileLineEdit.toolTip())
    elif getattr(self,'jobLabel',None) is not None:
      self.jobLabel.setToolTip(toolTip+'\n'+self.jobLabel.toolTip())
    for l in self.findChildren(QtGui.QLabel):
        l.setToolTip(toolTip)
        
  def handleFollowFrom(self,contextJobId,projectId):
    '''Handle change to the followFrom job'''
    #print 'CDataFileView.handleFollowFrom',self.model.objectName(),contextJobId,self.model.qualifiers('fromPreviousJob')
    import CCP4DbApi
    if self.model.qualifiers('fromPreviousJob'):
      if contextJobId is None:
        self.model.unSet()
      else:
        mimeType = self.model.qualifiers('mimeTypeName')
        if CCP4DbApi.FILETYPES_TEXT.count(mimeType):
          fileType = CCP4DbApi.FILETYPES_TEXT.index(mimeType)
        else:
          fileType = CCP4DbApi.FILETYPES_TEXT[0]
        subType =  self.model.qualifiers('requiredSubType')
        contentFlag =  self.model.requiredContent()
        #print 'CDataFileView.handleFollowFrom',self.model.objectName(),fileType,subType,type(subType),contentFlag,type(contentFlag)
          
        fileIdList = PROJECTSMANAGER().db().getFileByJobContext(contextJobId=contextJobId,fileType=fileType,subType=subType,contentFlag=contentFlag,projectId=projectId)
        #print 'CDataFileView.handleFollowFrom',self.model.objectName(),mimeType,fileType,fileIdList
        if len(fileIdList)>0:
          fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=fileIdList[0],mode=['jobid','filename','relpath','projectid','annotation','filecontent','filesubtype'])
          self.model.set( { 'baseName' : fileInfo['filename'],
                          'relPath' :  fileInfo['relpath'],
                          'project' : fileInfo['projectid'],
                          'annotation' : fileInfo['annotation'],
                          'dbFileId' :fileIdList[0],
                          'contentFlag' :fileInfo['filecontent'],
                          'subType' :fileInfo['filesubtype'] } )
        else:
          self.model.unSet()
    self.updateViewFromModel()
    self.validate()
                
  def reset(self):
    if self.editable:
      if self.ifJobCombo:
        self.loadJobCombo()
        #self.jobCombo.setCurrentIndex(0)
      else:
        self.fileLineEdit.clear()
    self.validate()

        
  def taskProjectId(self):
    # Widget may be in a task interface (in which case it has a parent CTaskWidget
    p =  self.parentTaskWidget()
    if p is not None:
      return p.projectId()
    else:
      #or in a report, in which case it has a parent CTaskFrame
      p=self.parentTaskFrame()
      if p is not None:
        return p.projectId()
      return None
    
  def getMenuDef(self):
    '''Return text list of actions required on menu'''
    if self.editable:
      menu = ['clear','view','sep','copy','paste','help']
      #menu = ['clear','view','sep','copy','paste','help']
    else:
      if self.role is not None and self.role == 'output':
        menu = ['view','sep','copy','editLabel','export','help']
      else:
        menu = ['view','sep','copy','export','help']
    if self._stacked: menu.insert(0,'handleStack')
    if self.ifInfo: menu.insert(menu.index('sep'),'annotate')
    return menu
  
  def getActionDef(self,name):
    '''Return definition of action to appear on menu'''
    if name == 'annotate':
      return dict (
        text = self.tr("Annotate "),
        tip = self.tr('Add information on file, particularly its provenance'),
        slot = self.openInfo,
        enabled = self.isImportedFile
      )
    elif name == 'export':
      return dict (
        text = self.tr("Export"),
        tip = self.tr('Copy and rename file'),
        slot = self.handleExport
      )
    elif name == 'editLabel':
      return dict (
        text = self.tr("Edit label"),
        tip = self.tr('Edit file label'),
        slot = self.handleEditLabel
      )
    else:
      return CComplexLineWidget.getActionDef(self,name)
    

  def filterText(self):
    # make the filters text for QFileDialog
    if self.model.qualifiers('mimeTypeDescription') is not NotImplemented:
      text = self.model.qualifiers('mimeTypeDescription') +' ('
    else:
      text = '('
    if self.model.qualifiers('fileExtensions') is not NotImplemented and len(self.model.qualifiers('fileExtensions'))>0:
      for ext in self.model.qualifiers('fileExtensions'):
        text = text + '*.'+ext+' '
      text = text[0:-1]+')'
    else:
      text = text + '*)'
    #print 'filterText',text
    return [ text ]


  def clear(self):
    '''Clear any selection'''
    if self.editable:
      if self.ifJobCombo:
        self.jobCombo.setCurrentIndex(0)
      else:
        self.fileLineEdit.setValue(None)
    self.updateModelFromView()
    #print 'CDataFileView.clear',self.model

  def isImportedFile(self):
    #print 'CDataFile.isImportedFile',self.model.objectName(),self.model.dbFileId.__str__()
    if not self.model.dbFileId.isSet():
      return True
    else:
      return False

  def handleExport(self):
    '''Handle menu option to export file'''
    import CCP4DbApi
    fileType = PROJECTSMANAGER().db().getFileInfo(fileId=self.model.dbFileId,mode='fileType')
    filters = MIMETYPESHANDLER().getMimeTypeInfo(fileType,'filter')
    defaultSuffix =  MIMETYPESHANDLER().getMimeTypeInfo(fileType,'fileExtensions')[0]
    #print 'CDataFileView.handleExport',fileType,filters,defaultSuffix
    import CCP4FileBrowser,functools
    fileBrowser = CCP4FileBrowser.CFileDialog(parent=self,
                                      title='Export '+self.model.baseName.__str__(),
                                      filters = [filters],
                                      defaultSuffix = defaultSuffix,
                                      fileMode = QtGui.QFileDialog.AnyFile)
    self.connect(fileBrowser,QtCore.SIGNAL('selectFile'),functools.partial(self.exportData,self.model.__str__(),str(self.model.dbFileId)))
    fileBrowser.show()

  def exportData(self,myFileName,fileId,exportFileName):
    # This is copy of code in CCP4ProjectWidget
    import os,shutil
    #print 'CDataFileView.exportData',myFileName,exportFileName,fileId
    if os.path.splitext(exportFileName) !=  os.path.splitext(myFileName):
      exportFileName = os.path.splitext(exportFileName)[0] +  os.path.splitext(myFileName)[1]
    try:
      shutil.copyfile(myFileName,exportFileName)
    except:
      e = CException(self.__class__,100,'From: '+str(myFileName)+' to: '+str(exportFileName),name=self.modelObjectPath())
      e.warningMessage('Copying file',parent=self)
    else:
      PROJECTSMANAGER().db().createExportFile(fileId=fileId,exportFilename=exportFileName)
      fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode=['jobid','projectname'])
      import CCP4DbUtils
      CCP4DbUtils.makeJobBackup(jobId=fileInfo['jobid'],projectName=fileInfo['projectname'])

  def handleEditLabel(self):
    import CCP4Widgets
    d = CCP4Widgets.CEditFileLabel(parent=self,fileId=self.model.dbFileId.__str__(),fileLabel=self.jobLabel.getValue())

  def handleDbFileUpdated(self,args):
    '''handle db signal that file attributes have changed - possible change to annotation'''
    if args['key'] != 'annotation': return
    if self.model is None or args['fileId'] != self.model.dbFileId.__str__(): return
    #print 'CDataFileView.handleDbFileUpdated updating'
    self.updateJobLabel()
    
    
  def setModel(self,model):
    #if model is not None: print 'CDataFileView.setModel',model.objectName(),model,isinstance(model,self.MODEL_CLASS)
    if isinstance(model,self.MODEL_CLASS) or model is None:
      if self.model is not None:
          self.connectUpdateViewFromModel(False)
      self.model = model
      if self.editable and self.ifJobCombo: self.loadJobCombo()
      self.updateViewFromModel()
      if model is not None: self.connectUpdateViewFromModel(True)


  def updateViewFromModel(self):
    if self.model is None:
      if self.ifJobCombo:
        pass
      else:
        self.fileLineEdit.setValue('')
      return
      
    #print 'CDataFileView.updateViewFromModel',self.model.objectName(),self.model
    self.blockSignals(True)
    if self.ifJobCombo:
      if self.editable:
        self.updateJobCombo()
      else:
        self.updateJobLabel()
    else:
      if self.model.project.isSet():
        projectName = str(self.model.project)
      else:
        projectName = None
      #print 'updateViewFromModel',projectName,type(projectName),'self.taskProject',self.taskProjectId()

      #print 'CDataFileView.updateViewFromModel',self.model.objectName(),type(self.model),self.model.annotaton.isSet(),self.model.annotaton.__str__()
      if self.model.annotation.isSet():
        fileName = self.model.annotation.__str__()
      else:
        fileName = self.model.__str__()
      self.fileLineEdit.setValue(fileName)
      self.blockSignals(False)
      
  def updateJobLabel(self):
    #print 'updateJobLabel',self.model.objectName(),self.model.isSet(), self.model,'*', self.role
    
    if not self.model.isSet():
      self.jobLabel.setValue('')
      return

    if self.role is None:
      container = self.model.parentContainer()
      if container is not None and container.objectName() == 'outputData':
        self.role = 'output'
      else:
       self.role = 'input'
    
      
    jobId = None
    jobInfo = {}
    if self.model.dbFileId.isSet():
      try:
        fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=str(self.model.dbFileId),mode=['taskname','jobnumber','annotation','relpath','filename'])
      except CException as e:
        print 'updateJobLabel', e.report()
        fileInfo = {}
      #print 'updateJobLabel',self.model.objectName(),fileInfo
      if fileInfo.get('annotation',None) is not None:
        if self.role == 'output':
          self.jobLabel.setValue(str(fileInfo['annotation']))
        else:
          self.jobLabel.setValue(fileInfo.get('jobnumber','')+' '+str(fileInfo['annotation']))
      elif fileInfo.get('taskname',None) is not None :
        if self.role == 'output':
          self.jobLabel.setValue(self.model.qualifiers('guiLabel'))
        else:
          self.jobLabel.setValue(fileInfo.get('jobnumber','')+' '+TASKMANAGER().getTitle(fileInfo['taskname']))
      else:
        if fileInfo.get('relpath',None) is None: fileInfo['relpath']=''
        import os
        self.jobLabel.setValue(os.path.join(fileInfo['relpath'],fileInfo.get('filename','')))
    elif self.model.annotation.isSet():
      self.jobLabel.setValue(str(self.model.annotation))
    elif not self.model.isSet():
      self.jobLabel.setValue('.. is not used')
    else:
      self.jobLabel.setValue(str(self.model))

  def updateJobCombo(self):
    '''Called from updateViewFromModel() to update the jobCombo when the model is changed''' 
    if self.model is None: return
    if getattr(self,'jobCombo',None) is None: return
    #print 'updateJobCombo',self.model.objectName(),self.model,self.jobCombo.currentIndex()
    
    self.jobCombo.blockSignals(True)
    if not self.model.isSet():
      #print 'CDataFileView.updateJobCombo model not set',self.model.objectName()
      self.jobCombo.setCurrentIndex(0)
      self.validate()
      self.jobCombo.blockSignals(False)
      return

    fileId = self.model.dbFileId.get()
    #print 'CDataFileView.updateJobCombo',self.model.objectName(),fileId
    if fileId is not None:
      try:
        if CCP4Data.varToUUID(self.jobCombo.itemData(self.jobCombo.currentIndex())) == fileId:
          self.jobCombo.blockSignals(False)
          self.validate()
          return
        #print 'skip update in updateJobCombo'
      except:
        pass
      indx = self.jobCombo.findData(QtCore.QVariant(fileId))
      
      #print 'CDataFileView.updateJobCombo getDbFileId',self.model.objectName(),self.model,fileId,indx
      if indx>=0:
        self.jobCombo.setCurrentIndex(indx)
      else:
        # This file not yet in the combo list
        try:
          fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode=['taskname','jobnumber','annotation','relpath','filename','projectid'])
        except:
          print 'Failed retrieving file data from Db data',fileId,self.model.objectPath(),self.model
          self.jobCombo.setCurrentIndex(0)
        else:
          #print 'CDataFileView.updateJobCombo fileInfo',fileInfo
          if fileInfo['projectid'] != self.taskProjectId():
            title = PROJECTSMANAGER().db().getProjectInfo(projectId=fileInfo['projectid'],mode='projectname')+' '
          else:
            title = ''
          if fileInfo['annotation'] is not None:
            title = title + str(fileInfo['jobnumber'])+' '+fileInfo['annotation']
          elif fileInfo['taskname'] is not None :
            title = title + str(fileInfo['jobnumber'])+' '+TASKMANAGER().getTitle(fileInfo['taskname'])
          else:
            if fileInfo['relpath'] is None: fileInfo['relpath']=''
            import os
            title = title + os.path.join(fileInfo['relpath'],fileInfo['filename'])
          self.jobCombo.insertItem(1,title,QtCore.QVariant(fileId))
          self.nComboFiles = self.nComboFiles + 1
          self.jobCombo.setCurrentIndex(1)
    else:
      if self.model.annotation.isSet():
        text = str(self.model.annotation)
      else:
        text = str(self.model)
      shortText = text[-CDataFileView.MAXCHARS:]
      # Beware menu text may cwbwgin with include the job number 
      indx = self.jobCombo.findText(shortText,QtCore.Qt.MatchEndsWith)
      if indx<0: indx = self.jobCombo.findData(QtCore.QVariant(text))
      #print 'CDataFileView.updateJobCombo indx',self.model.objectName(),text,indx
      if indx>=0:
        self.jobCombo.setCurrentIndex(indx)
        self.jobCombo.blockSignals(False)
        self.validate()
        return
      else:
        self.nComboFiles = self.nComboFiles + 1
        self.jobCombo.insertItem(1,shortText)
        self.jobCombo.setItemData(1,str(self.model),QtCore.Qt.UserRole+1)
        if self.model.__dict__.get('sourceFileName',None) is not None:
          #print 'updateJobCombo setting sourceFileName',self.model.__dict__['sourceFileName']
          self.jobCombo.setItemData(1,str(self.model.__dict__['sourceFileName']),QtCore.Qt.UserRole+2)
          if self.model.__dict__.get('sourceFileAnnotation',None) is not None:
            self.jobCombo.setItemData(1,str(self.model.__dict__['sourceFileAnnotation']),QtCore.Qt.UserRole+3)
        self.jobCombo.setCurrentIndex(1)
        self.validate()
    self.jobCombo.blockSignals(False)
        
  def setMaxChars(self):
    self.show()
    someText = 'foo/bar'    
    fm = QtGui.QFontMetrics(self.jobCombo.font())
    #print 'setMaxChars',
    layout = self.layout()
    #for i in range(layout.count()):
    #  print layout.itemAt(i).widget(),layout.itemAt(i).widget().width(),
    #print ' self',self.width()
    CDataFileView.MAXCHARS = (((self.width()-40) * len(someText)) / fm.width(someText))-8
    #print 'setMaxChars',self.jobCombo.width(),self.width(),fm.width(someText),CDataFileView.MAXCHARS

  def updateModelFromView1(self,text):
    self.updateModelFromView()
  
  def updateModelFromView(self):
    if self.model is None or not self.editable: return
    self.connectUpdateViewFromModel(mode=False)
    if hasattr(self,'jobCombo'):
      self.handleJobComboChange()          
    else:
      fullPath = self.fileLineEdit.getValue()     
      #print 'CDataFileView.updateModelFromView',fullPath
      self.model.setFullPath(fullPath)
    self.connectUpdateViewFromModel(mode=True)
    self.validate()
    #print 'CDataFileView.updateModelFromView',PREFERENCES().AUTO_INFO_ON_FILE_IMPORT
       
  def editSignal(self):
      return 'fileChanged'
            
  def openViewer(self,mode=None):
    '''Open file viewer'''
    import os
    if not self.model.isSet(): return
    fileName = self.model.fullPath.get()
    #print 'CDataFileView.openViewer',fileName,mode,self.taskProjectId()
    if not os.path.exists(fileName): return
    if  mode  in ['view_text']:
      WEBBROWSER().openFile(fileName=fileName,format='text/plain')   
    elif mode is not None and mode[0:4] == 'view' and len(mode)>4:
      # Open in external viewer
      LAUNCHER().openInViewer(mode[5:],fileName=fileName,projectId=self.taskProjectId(),guiParent=self)
    else:
      import CCP4WebBrowser
      CCP4WebBrowser.OPENFILE(fileName,toFront=True)
    
  
  def openBrowser(self):
    '''Open file browser - dependent on PREFERENCES().NATIVEFILEBROWSER'''
    if self.model.qualifiers('mimeTypeDescription') is not NotImplemented:
      title = 'Open '+self.model.qualifiers('mimeTypeDescription')
    else:
      title = 'Open file'
    ifInput = self.model.qualifiers('mustExist')
    try:
      if not ifInput:
        ifInput = (str(self.model.parentContainer().objectName()) == 'inputData')
    except:
      pass
    if PREFERENCES().NATIVEFILEBROWSER:
      if self.model.qualifiers('isDirectory') and ifInput:
        fileName = QtGui.QFileDialog.getExistingDirectory ( self, title, '' )
      elif ifInput:   
        fileName = QtGui.QFileDialog.getOpenFileName ( self, title, '', self.filterText()[0],'' )
      else:
        fileName = QtGui.QFileDialog.getSaveFileName ( self, title, '', self.filterText()[0],'' )
      if fileName is not None: self.handleBrowserOpenFile(str(fileName),None)
      return
    if self.browser is not None:
      self.browser.show()
      self.browser.raise_()
      return
    import CCP4FileBrowser
    try:
      if not ifInput:
        ifInput = (str(self.model.parentContainer().objectName()) == 'inputData')
    except:
      pass
    #print 'CDataFileView.openBrowser',self.model.parent().objectName(),ifInput, self.model.qualifiers('isDirectory'),self.model.qualifiers('mustExist')
    if self.model.qualifiers('isDirectory'):
      if self.model.qualifiers('mustExist'):
        fileMode = QtGui.QFileDialog.Directory
      else:
        fileMode = CCP4FileBrowser.CFileDialog.NewDirectory
      #print 'CDataFileView.openBrowser fileMode',fileMode
      self.browser = CCP4FileBrowser.CFileDialog(self,
         title=title,
         filters= self.filterText(),
         fileMode = fileMode,                              
         defaultSuffix='',defaultFileName='')
      self.connect(self.browser,QtCore.SIGNAL('selectDownloadedFile'),self.handleSelectDownloadedFile)
    elif ifInput:
      self.browser = CCP4FileBrowser.CFileDialog(self,
         title=title,
         filters= self.filterText(),
         defaultSuffix='',defaultFileName='')
      #   fileLabel = self.model.qualifiers('fileLabel'),                                     
      self.browser.setStyleSheet("") 
      self.connect(self.browser,QtCore.SIGNAL('selectDownloadedFile'),self.handleSelectDownloadedFile)
      downloadModes = self.model.qualifiers('downloadModes')
      if downloadModes is not None and downloadModes is not NotImplemented and len(downloadModes)>0:
        projectId = None
        tw = self.parentTaskWidget()
        if tw is not None: projectId = tw.projectId()
        
        self.browser.setDownloadMode(modeList=downloadModes,projectId=projectId)
    else:
      if len(self.model.qualifiers('fileExtensions'))>0:
        defaultSuffix= self.model.qualifiers('fileExtensions')[0]
      else:
        defaultSuffix = ''
      self.browser = CCP4FileBrowser.CFileDialog(self,
         title=title,
         filters= self.filterText(),
         defaultSuffix=defaultSuffix,
         fileMode = QtGui.QFileDialog.AnyFile )
      self.connect(self.browser,QtCore.SIGNAL('selectDownloadedFile'),self.handleSelectDownloadedFile)
    self.browser.show()
    
  def closeBrowser(self):
    # Programmatically close the browser
    if self.browser is not None: self.browser.close()

  # The method called from connect as pass through to handleBrowserOpenFile
  # Is here to ensure the arguments from the connect are consistent 
  def handleSelectDownloadedFile(self,fileName,downloadInfo):
    self.handleBrowserOpenFile(fileName,downloadInfo)
    
  def handleBrowserOpenFile(self,fileName,downloadInfo,autoInfoOnFileImport=True,validate=True,updateView=True):
    '''Handle importing a file seelcted by the file browser (or other route)'''
    #print 'CDataFileView.handleBrowserOpenFile',fileName,downloadInfo
    import os
    if downloadInfo is not None and downloadInfo.get('code',None) is not None:
      sourceFileAnnotation = 'Downloaded from '+str(downloadInfo.get('source','unknown'))+' Id: '+str(downloadInfo.get('code','unknown'))
    else:
      guiLabel = 'Object'
      if self.model.qualifiers('guiLabel') is not NotImplemented: guiLabel = self.model.qualifiers('guiLabel')
      if self.parentTaskWidget() is not None and self.parentTaskWidget().jobNumber() is not None: 
        sourceFileAnnotation = guiLabel + ' loaded from ' + os.path.split(fileName)[1] + ' by job ' + self.parentTaskWidget().jobNumber()
      else:
        sourceFileAnnotation = ''
    try:
      self.connectUpdateViewFromModel(False)
      self.model.setFullPath(fileName)
      if len(sourceFileAnnotation)>0: self.model.annotation = sourceFileAnnotation
      self.connectUpdateViewFromModel(True)
    except CException as e:
      import CCP4XtalData
      if isinstance(self.model,CCP4XtalData.CMiniMtzDataFile):
        pass
      else:
        e.warningMessage('Attempting to select file',parent=self)
    #print 'CDataFileView.handleBrowserOpenFile fullPath',self.model.fullPath
    if updateView: self.updateViewFromModel()
    if validate: self.validate()
    #print 'CDataFileView.handleBrowserOpenFile',autoInfoOnFileImport,PREFERENCES().AUTO_INFO_ON_FILE_IMPORT,self.model.dbFileId.isSet()
    if (self.autoInfoOnFileImport and autoInfoOnFileImport and PREFERENCES().AUTO_INFO_ON_FILE_IMPORT) and not self.model.dbFileId.isSet():
      self.openInfo(sourceFileAnnotation=sourceFileAnnotation)
    else:
      # Save to be used by PROJECTSMANAGER.importFiles() to load to Db when user runs the task
      if len(sourceFileAnnotation)>0: self.model.__dict__['sourceFileAnnotation'] = sourceFileAnnotation
    #print 'CDataFileView.handleBrowserOpenFile DONE'
    return

  def handleFileUpdated(self,args):
    '''Update combo box if file annotation changed'''
    if not args['key'] == 'annotation': return
    if self.editable and self.ifJobCombo:
      indx = self.jobCombo.findData(QtCore.QVariant(args['fileId']))
      #print 'CDataFileView.handleFileChanged',fileInfo,indx
      if indx<0: return
      text = str(self.jobCombo.itemText(indx)).split()[0] + ' '+args['value']
      self.jobCombo.setItemText(indx,text)
    elif not self.editable:
      pass
    

  def loadJobCombo(self):
    '''Load list of recent data objects into combo box'''
    #print 'loadJobCombo',self.taskProjectId(),self.model
    #if self.model is not None: print self.model.objectName(), self.model.qualifiers
    if not self.editable or not self.ifJobCombo: return
    self.jobCombo.blockSignals(True)
    if self.jobCombo.count()>0:
      currentText = str(self.jobCombo.currentText())
    else:
      currentText = ''
    resetIndex = 0
    self.jobCombo.clear()
    self.nComboFiles = 0
    if self.model is None or self.model.qualifiers('allowUndefined'):
      self.jobCombo.addItem('..is not used',QtCore.QVariant(-1))
    else:
      self.jobCombo.addItem('..must be selected',QtCore.QVariant(-1))
    if self.taskProjectId() is not None and self.model is not None:
      jobListInfo =PROJECTSMANAGER().db().getJobsWithOutputFiles(projectId=self.taskProjectId(),fileType = self.model.qualifiers('mimeTypeName'),subType=self.model.qualifiers('requiredSubType'),contentFlag=self.model.requiredContent(),importFiles=True)
      #print 'loadJobCombo',self.model.objectName(),jobListInfo
      '''
      preferredFollowFrom = self.model.qualifiers('preferredFollowFrom')
      print 'loadJobCombo',self.model.objectName(),jobListInfo,preferredFollowFrom
      if len(preferredFollowFrom)>0:
        while self.nComboFiles<10 and indx<len(jobListInfo):
          if preferredFollowFrom.count(jobListInfo[indx]['taskname']+','+jobListInfo[indx]['jobparamname']):
            if jobListInfo[indx]['annotation'] is not None:
              title = jobListInfo[indx]['annotation']
            else:
              title = MIMETYPESHANDLER().getMimeTypeInfo(name=jobListInfo[indx]['filetype'],info='description')+' from '+TASKMANAGER().getTitle(jobListInfo[indx]['taskname'])
            self.jobCombo.addItem(str(jobListInfo[indx]['jobnumber'])+' '+title,QtCore.QVariant(jobListInfo[indx]['fileid']))
            self.nComboFiles = self.nComboFiles + 1
            jobListInfo.pop(indx)
          else:
            indx += 1
      '''
      for item in jobListInfo[0:10]:
        if item['annotation'] is not None:
          title = item['annotation']
        else:
          title = MIMETYPESHANDLER().getMimeTypeInfo(name=item['filetype'],info='description')+' from '+TASKMANAGER().getTitle(item['taskname'])
        self.jobCombo.addItem(str(item['jobnumber'])+' '+title,QtCore.QVariant(item['fileid']))
        self.nComboFiles = self.nComboFiles + 1
        if str(item['jobnumber'])+' '+title == currentText: resetIndex = self.nComboFiles
      if len(jobListInfo)>10:
        self.jobCombo.insertSeparator(1000)
        self.jobCombo.addItem('More data..')
    #print 'loadJobCombo',self.model.objectName(),resetIndex
    self.jobCombo.setCurrentIndex(resetIndex)
    self.jobCombo.blockSignals(False)

  def handleJobComboChange(self,indx0=None):
    '''Handle a user selection from combo box'''
    #print 'handleJobComboChange',indx0
    if indx0 is None:
      indx = int(self.jobCombo.currentIndex())
    else:
      indx = int(indx0)
    if indx == 0:
      self.model.unSet()
    elif str(self.jobCombo.itemText(indx)) == 'More data..':
      self.jobCombo.blockSignals(True)
      self.jobCombo.setCurrentIndex(0)
      self.jobCombo.blockSignals(False)
      if self.jobChooser is None:
        self.jobChooser = CJobChooser(self)
        self.connect(self.jobChooser,QtCore.SIGNAL('fileSelected'),self.loadFileFromDb)
      self.jobChooser.populate()
      self.jobChooser.show()
    else:
      #print 'handleJobComboChange < nComboFiles',indx
      self.connectUpdateViewFromModel(False)
      fileId = CCP4Data.varToUUID(self.jobCombo.itemData(indx))
      #print 'CDataFileView.handleJobComboChange fileId',fileId
      if fileId is not None and len(fileId)>0:
        self.loadFileFromDb(fileId,annotation=str(self.jobCombo.currentText()))     
      else:
        #quuid
        self.model.blockSignals(True)
        self.model.contentFlag.unSet()
        fullPath = CCP4Data.varToUUID(self.jobCombo.itemData(indx,QtCore.Qt.UserRole+1))
        #print 'CDataFileView.handleJobComboChange fullPath',fullPath
        self.model.setFullPath(fullPath)
        self.model.annotation.set(str(self.jobCombo.currentText()))
        self.model.blockSignals(False)
        self.model.emitDataChanged()
        try:
          self.model.__dict__['sourceFileName'] = self.jobCombo.itemData(indx,QtCore.Qt.UserRole+2).toString().__str__()
          #print 'handleJobComboChange sourceFileName',self.model.__dict__['sourceFileName']
          self.model.__dict__['sourceFileAnnotation'] = self.jobCombo.itemData(indx,QtCore.Qt.UserRole+3).toString().__str__()
        except:
          pass
        self.model.blockSignals(False)
      self.connectUpdateViewFromModel(True)
      self.validate()
    

  def loadFileFromDb(self,fileId,annotation=None):
    '''Update model based on db fileId from eith the combo box or the database search tool'''
    #print 'CDataFileView.loadFileFromDb',fileId
    fileId = str(fileId)
    if fileId is None:
      self.model.unSet()
    else:
      fileInfo = 'UNSET'
      try:
        fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId,mode=['projectid','relpath','filename','annotation','filesubtype','filecontent','filetype'])
        #print 'CDataFileView.loadFileFromDb',fileInfo
        self.model.blockSignals(True)
        self.model.set(project=fileInfo['projectid'],relPath=fileInfo['relpath'],baseName=fileInfo['filename'])
        if annotation is not None:
          self.model.annotation.set(annotation)
        else:
          self.model.annotation.set(fileInfo['annotation'])
        #print 'CDataFileView.loadFileFromDb set filename'
        self.model.subType=fileInfo.get('filesubtype',None)
        #print 'CDataFileView.loadFileFromDb set subType'
        self.model.contentFlag=fileInfo.get('filecontent',None)
        #print 'CDataFileView.loadFileFromDb set contentFlag',self.model.contentFlag
        self.model.dbFileId = fileId
        #print 'CDataFileView.loadFileFromDb set dbFileId'
        self.model.blockSignals(False)
        self.model.emitDataChanged()
      except:
        print 'Error retrieving file info from Db (CDataFileView.loadFileFromDb) for',fileId,self.model.objectPath(),str(self)
        self.model.unSet()
        self.jobCombo.blockSignals(True)
        self.jobCombo.setCurrentIndex(0)
        self.jobCombo.blockSignals(False)

  def handleJobFinished(self,args):
    #print 'CDataFileView.handleJobFinished',args
    '''Add new file from just finished job to the combo box'''
    try:
      if args.get('projectId','x') != self.parentTaskWidget().projectId(): return
    except:
      return
    if not self.editable or not self.ifJobCombo or args.get('parentJobId','XXXX') is not None: return 
    self.loadJobCombo()
  
  def showEditAnnotation(self):
    pass

  def openInfo(self,label=None,sourceFileAnnotation=''):
    '''Open the file info window for user to enter provenance of newly imported file'''
    #print 'CDataFileView.openInfo',self.model,self.model.exists(),self.model.getSourceFileName()
    if not self.model.exists(): return
    if self.model.dbFileId.isSet():
      importId = PROJECTSMANAGER().db().getFileInfo(fileId=self.model.dbFileId,mode='importId')
    else:
      importId = None
    #print 'CDataFileView.openInfo importId',importId
    import CCP4ImpExpWidgets
    self.infoWidget = CCP4ImpExpWidgets.CImportInfoDialog(self,self.model,importId=importId,label=label,sourceFileAnnotation=sourceFileAnnotation)
    self.infoWidget.setProjectId(self.taskProjectId())
    self.infoWidget.show()
    self.infoWidget.raise_()

  def acceptDropData(self,textData):
    '''Reimplement drop to handle drag from outside i2'''
    #print 'CDataFileView.acceptDropData',textData
    from lxml import etree
    tree = etree.fromstring(textData)
    if tree.find('dbFileId') is not None and len(tree.find('dbFileId').text)>0:
      CComplexLineWidget.acceptDropData(self,textData)
    else:
      import os
      try:
        path = os.path.join(tree.xpath('//relPath')[0].text.__str__(),tree.xpath('//baseName')[0].text.__str__())
        print 'CDataFileView.acceptDropData path',path
      except:
        pass
      else:
        self.handleBrowserOpenFile(path,{})

  def createMimeData(self):
    '''Reimplement drag/drop to add url for drop outside i2'''
    # For CDataFiles add mimeType and url
    mimeData = CComplexLineWidget.createMimeData(self)
    url = QtCore.QUrl()
    url.setPath(self.model.__str__())
    mimeData.setUrls([url])
    return mimeData

  def openDatabaseBrowser(self):
    '''Open window to select file from another project'''
    if self.databaseBrowser is not None:
      self.databaseBrowser.show()
      self.databaseBrowser.raise_()
      return
    import CCP4DatabaseBrowser
    self.databaseBrowser = CCP4DatabaseBrowser.CDatabaseBrowser(parent=self,title='Open file from another project',
                                   fileType=self.model.qualifiers('mimeTypeName'),projectId=self.taskProjectId())
    self.connect(self.databaseBrowser,QtCore.SIGNAL('fileSelected'),self.handleDatabaseBrowserOpenFile)

  def handleDatabaseBrowserOpenFile(self,dbFileId):
    '''Handle file selected in database browser'''
    #print 'CDataFileView.handleDatabaseBrowserOpenFile',dbFileId
    self.databaseBrowser.hide()
    self.connectUpdateViewFromModel(False)
    self.loadFileFromDb(dbFileId)
    self.connectUpdateViewFromModel(True)
    self.updateViewFromModel()
    self.validate(isValid=True)


    
class CJobChooser(QtGui.QDialog):
  '''Popout window to display all files when CDataFileView.jobCombo not showing enough'''

  def __init__(self,parent):
    QtGui.QDialog.__init__(self,parent)
    self.setLayout(QtGui.QHBoxLayout())
    self.list = QtGui.QListWidget(self)
    self.layout().addWidget(self.list)
    self.connect(self.list,QtCore.SIGNAL("itemDoubleClicked(QListWidgetItem *)"),self.handleSelection)

  def populate(self):
    self.list.clear()
    jobListInfo =PROJECTSMANAGER().db().getJobsWithOutputFiles(projectId=self.parent().taskProjectId(),fileType = self.parent().model.qualifiers('mimeTypeName'),contentFlag=self.parent().model.requiredContent(),importFiles=True)
    #print 'CJobChooser.populate',jobListInfo
    for item in jobListInfo:
      if item['annotation'] is not None:
        title = item['annotation']
      else:
        title = TASKMANAGER().getTitle(item['taskname'])
      listItem = QtGui.QListWidgetItem(str(item['jobnumber'])+' '+title)
      listItem.setData(QtCore.Qt.UserRole,QtCore.QVariant(item['fileid']))
      self.list.addItem(listItem)

  def handleSelection(self,listItem):
    fileId = CCP4Data.varToUUID(listItem.data(QtCore.Qt.UserRole))
    if fileId is not None:
      self.emit(QtCore.SIGNAL('fileSelected'),fileId)
      self.close()


class CStackedWidget(CViewWidget):
  '''Use QStackedLayout to enable alternative widgets in same space in task widget'''

  def __init__(self,parent):
    CViewWidget.__init__(self,parent)
    import CCP4TaskWidget
    self.setLayout(QtGui.QStackedLayout())

  def handleStack(self,state=None):
    if state is None: state = not self.isAltViewOpen()
    indx =self.layout().currentIndex()

    if self.layout().currentWidget() is not None:
      self.layout().currentWidget().setSizePolicy(QtGui.QSizePolicy.Ignored,
                                         QtGui.QSizePolicy.Ignored)
    if state:
      self.layout().setCurrentIndex(1)
    else:
      self.layout().setCurrentIndex(0)
    self.layout().currentWidget().setSizePolicy(QtGui.QSizePolicy.Expanding,
                                       QtGui.QSizePolicy.Expanding)
    self.adjustSize()

    
  def createIconButton(self):
    icon = QtGui.QIcon(PIXMAPMANAGER().getPixmap('view_forward'))
    stackButton = QtGui.QToolButton(self)
    stackButton.setIcon(icon)
    stackButton.setToolTip('Alternate view')
    stackButton.setIconSize(QtCore.QSize(ICONBUTTONSIZE,ICONBUTTONSIZE))
    stackButton.setMaximumHeight(ICONBUTTONSIZE)
    stackButton.setMaximumWidth(ICONBUTTONSIZE)
    self.connect(stackButton,QtCore.SIGNAL('clicked()'),self.handleStack)
    return stackButton
    
  def isAltViewOpen(self):
    indx = self.layout().currentIndex()
    #print 'CDataFileView.isAltViewOpen',indx
    return self.layout().currentIndex() == 1

  def addWidget(self,widget):
    self.layout().addWidget(widget)
    
  def getValue(self):
    value = {}
    for i in range(self.layout().count()):
      w = self.layout().widget(i)
      value.update(w.getValue)
    #print 'CStackedWidget.getValue',value
    return value

  def setValue(self,value):
    #print 'CStackedWidget.setValue',value
    for i in range(self.layout().count()):
      w = self.layout().widget(i)
      w.setValue(value)



  def updateViewFromModel(self):
    for indx in range(self.layout().count()):
      self.layout().widget(indx).updateViewFromModel()
    self.validate()

  def updateModelFromView(self):
    for indx in range(self.layout().count()):
      self.layout().widget(indx).updateModelFromView()
      
  
class CListViewListWidget( QtGui.QListWidget):
  '''Component of CListView - use a QListWidget to show items in list'''
  def __init__(self,parent=None,qualifiers={}):
    QtGui.QListWidget.__init__(self,parent)
    self.setDragEnabled(True)
    if qualifiers.get('editable',True):
      self.setAcceptDrops(True)
      self.setSelectionMode( QtGui.QListWidget.SingleSelection)
      #self.setDragDropMode(QtGui.QAbstractItemView.DropOnly)
      #self.setDefaultDropAction(QtCore.Qt.CopyAction)
      self.popupMenu = None
      self.connect(self,QtCore.SIGNAL('rightMousePress'),self.showContextMenu)
      self.connect(self,QtCore.SIGNAL('leftMousePress'),self.openEdit)
    else:
      self.setSelectionMode( QtGui.QListWidget.NoSelection)

  def makeItem(self,indx):
    if indx>=0:
      self.insertItem(indx,'')
    else:
      self.addItem('')
    print 'CListViewListWidget.makeItem',indx,self.count(),repr(self)

  def setCurrentRow(self,row):
    self.blockSignals(True)
    self.setCurrentItem(self.item(row))
    self.blockSignals(False)


  def mousePressEvent(self,event):
    #print 'CListViewListWidget.mousePressEvent',event.button()
    if event.button() == QtCore.Qt.RightButton:
      self.emit(QtCore.SIGNAL('rightMousePress'),event)
      event.accept()
    
    elif event.button() == QtCore.Qt.LeftButton:
      self.emit(QtCore.SIGNAL('leftMousePress'),event)
      event.accept()
    
    else:
      QtGui.QListWidget.mousePressEvent(self,event)

  def openEdit(self,event):
    indx = self.indexAt(event.pos())
    self.emit(QtCore.SIGNAL('edit'),indx.row(),indx.row())
    
  def showContextMenu(self,event):
    import functools
    row = self.indexAt(event.pos()).row()
    column = self.indexAt(event.pos()).column()
    if self.popupMenu is None:
      self.popupMenu = QtGui.QMenu(self)
    else:
      self.popupMenu.clear()
    a = self.popupMenu.addAction('Edit')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('edit'),row,column))
    a = self.popupMenu.addAction('Insert above')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),row))
    a = self.popupMenu.addAction('Insert below')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),row+1))
    a = self.popupMenu.addAction('Delete')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('delete'),row))
                   
    self.popupMenu.popup(event.globalPos())


class CListViewTableWidget( QtGui.QTableWidget):

  '''Component of CListView - use a QTableWidget to show items in list'''
  def __init__(self,parent=None,qualifiers={}):
    #print 'CListViewTableWidget.__init__',qualifiers
    QtGui.QTableWidget.__init__(self,parent)
    #self.setDragEnabled(True)
    #self.setAcceptDrops(True)
    self.setSelectionMode( QtGui.QAbstractItemView.SingleSelection)
    self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
    self.setColumnCount(len(qualifiers['tableItems']))
    self.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
    self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
    if qualifiers.has_key('columnHeaders'):
      labelList = QtCore.QStringList()
      for item in  qualifiers['columnHeaders']: labelList.append(item)
      self.setHorizontalHeaderLabels(labelList)
    else:
      self.setHorizontalHeader(None)
    self.verticalHeader().hide()
    #self.setDragDropMode(QtGui.QAbstractItemView.DropOnly)
    #self.setDefaultDropAction(QtCore.Qt.CopyAction)
    self.popupMenu = None
    self.connect(self,QtCore.SIGNAL('rightMousePress'),self.showContextMenu)
    self.connect(self,QtCore.SIGNAL('currentItemChanged(QTableWidgetItem * ,QTableWidgetItem * )'),self.handleItemChanged)
    #print 'CListViewTableWidget.__init__ done'

  def handleItemChanged(self,current,previous):
    #print 'CListViewTableWidget.handleItemChanged',current,previous
    if current is not None:
      self.emit(QtCore.SIGNAL('currentRowChanged(int)'),int(current.row()))
   
                 
  def makeItem(self,indx):
    self.setRowCount(int(self.rowCount())+1)

  def setCurrentRow(self,row):
    self.blockSignals(True)
    self.setCurrentCell(row,0)
    self.blockSignals(False)

  def keyReleaseEvent(self,event):
    #print 'CListViewTableWidget.keyReleaseEvent',event.key()
    if event.key() == QtCore.Qt.Key_Delete:
      row = self.itemAt(self.cursor().pos()).row()
      #print 'CListViewTableWidget.keyReleaseEvent row',row
      self.emit(QtCore.SIGNAL('delete'),row)
                
  def mousePressEvent(self,event):
    if event.button() == QtCore.Qt.RightButton:
      self.emit(QtCore.SIGNAL('rightMousePress'),event)
      event.accept()
    else:
      QtGui.QTableWidget.mousePressEvent(self,event)

  def showContextMenu(self,event):
    import functools
    row = self.indexAt(event.pos()).row()
    if self.popupMenu is None:
      self.popupMenu = QtGui.QMenu(self)
    else:
      self.popupMenu.clear()
    a = self.popupMenu.addAction('Edit')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('edit'),row))
    a = self.popupMenu.addAction('Insert above')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),row))
    a = self.popupMenu.addAction('Insert below')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),row+1))
    a = self.popupMenu.addAction('Delete')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('delete'),row))
                   
    self.popupMenu.popup(event.globalPos())


"""
    Probably not used - CTreeView (below) used instead
class CListViewTreeWidget( QtGui.QTreeWidget):

  '''Component of CListView - use a QTreeWidget to show items in list'''
  def __init__(self,parent=None,qualifiers={}):
    #print 'CListViewTableWidget.__init__',qualifiers
    QtGui.QTreeWidget.__init__(self,parent)
    #self.setDragEnabled(True)
    #self.setAcceptDrops(True)
    self.setSelectionMode( QtGui.QAbstractItemView.SingleSelection)
    self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
    self.setColumnCount(len(qualifiers['columnHeaders']))
    self.header().setResizeMode(QtGui.QHeaderView.Stretch)
    self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
    if qualifiers.has_key('columnHeaders'):
      labelList = QtCore.QStringList()
      for item in  qualifiers['columnHeaders']: labelList.append(item)
      self.setHeaderLabels(labelList)
    else:
      self.setHorizontalHeader(None)
    #self.setDragDropMode(QtGui.QAbstractItemView.DropOnly)
    #self.setDefaultDropAction(QtCore.Qt.CopyAction)
    self.popupMenu = None
    self.connect(self,QtCore.SIGNAL('rightMousePress'),self.showContextMenu)
    self.connect(self,QtCore.SIGNAL('currentItemChanged(QTreeWidgetItem * ,QTreeWidgetItem * )'),self.handleItemChanged)
    #print 'CListViewTableWidget.__init__ done'

  def itemToIndices(self,item):
    if item is None: return None,None
    if item.parent() is None:
      i1 = self.indexOfTopLevelItem(item)
      i2 = None
    else:
      i1 = self.indexOfTopLevelItem(item.parent())
      i2 = item.parent().indexOfChild(item)
    return i1,i2

  def indicesToItem(self,indx0,indx1=None):
    if indx1 is None:
      return self.topLevelItem(indx0)
    else:
      return self.topLevelItem(indx0).child(indx1)
      
  def handleItemChanged(self,current,previous):
    #print 'CListViewTableWidget.handleItemChanged',current,previous
    if current is not None:
      i1,i2 = self.itemToIndices(current)
      self.emit(QtCore.SIGNAL('currentRowChanged'),i1,i2)
   
                 
  def makeItem(self,indx0,indx1=None):
    #print 'CListViewTreeWidget.makeItem',indx0,indx1
    if indx0 is None:
      parent = self.indicesToItem(self.topLevelItemCount()-1)
    elif indx0<0:
      # Just add an item - no specific index
      parent = QtGui.QTreeWidgetItem()
      self.addTopLevelItem(parent)
    else:       
      parent = self.indicesToItem(indx0,None)
      if parent is not None and indx1 is None:
        # Asking to insert a top level item
        parent = QtGui.QTreeWidgetItem()
        self.insertTopLevelItem(indx0,parent)
        #print 'CListViewTreeWidget.makeItem insertTopLevelItem',indx0,self.topLevelItemCount()
      else:
        while parent is None:
          item = QtGui.QTreeWidgetItem()
          self.addTopLevelItem(item)
          parent = self.indicesToItem(indx0,None)
          #print 'CListViewTreeWidget.makeItem append item',indx0,self.topLevelItemCount(),parent
        
    if indx1 is None:
      return parent
    elif indx1 < 0:
       item = QtGui.QTreeWidgetItem()
       parent.addChild(item)
       return item
    else:
      child = self.indicesToItem(indx0,indx1)
      if child is not None:
        # Insert at this position
        child =  QtGui.QTreeWidgetItem()
        parent.insertChild(indx1,child)
        #print 'CListViewTreeWidget.makeItem insert child item',indx1,child,parent.indexOfChild(child)
      else:
        # append until have item with these indices
        while child is None:
          item = QtGui.QTreeWidgetItem()
          parent.addChild(item)
          child = self.indicesToItem(indx0,indx1)
          #print 'CListViewTreeWidget.makeItem append child item',indx0,indx1,parent.childCount(),child
      return child

  def setCurrentRow(self,indx0,indx1=None):
    item = self.indicesToItem(indx0,indx1)
    if item is None: return
    self.blockSignals(True)
    self.setCurrentItem(item)
    self.blockSignals(False)

  def keyReleaseEvent(self,event):
    #print 'CListViewTableWidget.keyReleaseEvent',event.key(),QtCore.Qt.Key_Delete
    if event.key() == QtCore.Qt.Key_Delete:
      pos = self.mapFromGlobal(self.cursor().pos())-QtCore.QPoint(0,self.header().height())
      item = self.itemAt(pos)
      #print 'CListViewTableWidget.keyReleaseEvent item',pos,item
      if item is None: return
      indx0,indx1 = self.itemToIndices(item)
      #print 'CListViewTableWidget.keyReleaseEvent',indx0,indx1
      self.emit(QtCore.SIGNAL('delete'),indx0,indx1)
      event.accept()
    else:
      event.ignore()
                
  def mousePressEvent(self,event):
    if event.button() == QtCore.Qt.RightButton:
      self.emit(QtCore.SIGNAL('rightMousePress'),event)
      event.accept()
    else:
      QtGui.QTreeWidget.mousePressEvent(self,event)

  def showContextMenu(self,event):
    if not self.parent().editable: return
    import functools
    indx0,indx1 = self.itemToIndices(self.itemAt(event.pos()))
    if self.popupMenu is None:
      self.popupMenu = QtGui.QMenu(self)
    else:
      self.popupMenu.clear()
    a = self.popupMenu.addAction('Edit')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('edit'),indx0,indx1,None))
    name,label,childLabelList = self.parent().getLabels(indx0,indx1)
    if label is None: label = ''
    if indx1 is None:
      # Is a top level 
      a = self.popupMenu.addAction('Insert '+label+' above')
      self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),indx0,indx1,name))
      a = self.popupMenu.addAction('Insert '+label+' below')
      self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),indx0+1,indx1,name))
      for childName,childLabel in childLabelList:
        a = self.popupMenu.addAction('Add '+childLabel)
        self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),indx0,None,childName))
    else:
      a = self.popupMenu.addAction('Insert '+label+' above')
      self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),indx0,indx1,name))
      a = self.popupMenu.addAction('Insert '+label+' below')
      self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),indx0,indx1+1,name))
    a = self.popupMenu.addAction('Delete')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('delete'),indx0,indx1,None))
                   
    self.popupMenu.popup(event.globalPos())
"""


class CListView(CComplexLineWidget):
  LINE_HEIGHT = 32
  MODEL_CLASS = CCP4Data.CList
  SIDE_BOX = False
  def __init__(self,parent=None,model=None,qualifiers={},editorQualifiers={}):
    #print 'CListView qualifiers',qualifiers
    qualis = {'gridLayout' : True, 'iconName' : 'List' }
    qualis.update(qualifiers)
    #print 'CListView init',repr(model)
    CComplexLineWidget.__init__(self,parent=parent,qualifiers=qualis)
    self.setModel(model)
    self.viewMode = qualifiers.get('mode','list')
    if self.viewMode in ['table','tree']: self.tableItems = qualifiers['tableItems']
    #print 'CListView.__init__ viewMode',self.viewMode,qualis
      
    self.layout().removeWidget(self.iconButton)
    title = qualis.get('title',None)
    if title is None: title = qualis.get('label',None)
    line = QtGui.QHBoxLayout()
    if self.iconButton is not None:
      line.addWidget(self.iconButton)
      self.connect(self.iconButton,QtCore.SIGNAL('leftMousePress'),self.updateMenu)
    if title is not None: line.addWidget(CItalicLabel(title,self))
    import CCP4GuiUtils
    self.showListButton = QtGui.QToolButton(self)
    self.showListButton.setText('Show list')
    self.showListButton.setToolTip('Show a list to enable entering more than one item')
    self.connect(self.showListButton,QtCore.SIGNAL('released()'),self.setListVisible)
    line.insertWidget(1,self.showListButton)
    line.addStretch(2)
    if CListView.SIDE_BOX:
      self.layout().addLayout(line,0,0,1,2)
    else:
      self.layout().addLayout(line,0,0)

    # Put menu on left mouse button (as well as right)
    if self.iconButton is not None:
      self.connect(self.iconButton,QtCore.SIGNAL('leftMousePress'),self.updateMenu)
      self.iconButton.setToolTip('Click for menu')

    #print 'CListView',self.viewMode
    if self.viewMode =='table':
      self.listWidget = CListViewTableWidget(self,qualifiers=qualifiers)
      #self.connect(self.listWidget,QtCore.SIGNAL('clicked(const QModelIndex &)'),self.handleListClicked)
    elif self.viewMode =='tree':
      self.listWidget = CListViewTreeWidget(self,qualifiers=qualifiers)
      #self.connect(self.listWidget,QtCore.SIGNAL('itemClicked(QTreeWidgetItem*,int)'),self.handleTreeClicked)
    else:
      self.listWidget = CListViewListWidget(self,qualifiers=qualifiers)
      #self.connect(self.listWidget,QtCore.SIGNAL('clicked(const QModelIndex &)'),self.handleListClicked)
    if CListView.SIDE_BOX:
      self.layout().addWidget(self.listWidget,1,1)
    else:
      self.layout().addWidget(self.listWidget,1,0)
    self.populateListWidget(resize=True)

    self.itemAddedToModel = False
    self.editItemIndex = 0
    if self.editable:
      import functools
      self.buttonBox = self.createListButtons()
      #self.editButton = self.buttonBox.layout().itemAt(2).widget()
      self.editButton = None
      if  CListView.SIDE_BOX:
        self.layout().addWidget(self.buttonBox,1,0)
      else:
        self.layout().addWidget(self.buttonBox,2,0)
      

      #if len(self.model) == 0:
      #  self.model.addItem()
      #  self.itemAddedToModel = True

      self.editorStack = QtGui.QStackedWidget()
      if CListView.SIDE_BOX:
        self.layout().addWidget(self.editorStack,2,0,1,2)
      else:
        self.layout().addWidget(self.editorStack,3,0)

      import CCP4DataManager
      editorClassName = qualis.get('editorClassName',None)
      if editorClassName is not None:
        editorClass = CCP4DataManager.DATAMANAGER().getClass(className=editorClassName)
      else:
        editorClass = None
      if editorClass is None:
        editorClass = CCP4DataManager.DATAMANAGER().getWidgetClass(modelClass=model.subItemClass())
      #print 'CListView.init',model.objectName(),editorClass
      if editorClass is not None:
        if model is not None:
          ediQualis = model.subItemQualifiers()
        else:
         ediQualis = {}
        #print 'CListView.init',model.objectName(),ediQualis,len(model),self.editItemIndex
        ediQualis.update(editorQualifiers)
        self.editor = editorClass(self,qualifiers=ediQualis)
        #print 'CListView.init editor',self.editor
        self.editorStack.addWidget(self.editor)
        self.editorStack.setVisible(True)

      self.setListVisible(qualifiers.get('listVisible',False))

      if self.model is not None and len( self.model)>0:
         self.handleRowChange(row=0,force=True)
         self.connectDataChanged()
      self.connect(self.listWidget,QtCore.SIGNAL('currentRowChanged(int)'),self.handleRowChange)
      self.connect(self.listWidget,QtCore.SIGNAL('edit'),self.handleRowChange)
      self.connect(self.listWidget,QtCore.SIGNAL('insert'),self.addLine)
      self.connect(self.listWidget,QtCore.SIGNAL('delete'),self.deleteLine)
      self.setEditorVisible(True)
      


  def setModel(self,value):
    self.model = value
    if self.model is not None:
      if self.model.__len__() == 0:
        self.model.addItem()
        if self.model.qualifiers('listMinLength') is NotImplemented or self.model.qualifiers('listMinLength')==0:
          #print 'CListView.setModel setting allowUndefined',repr(self.model),self.model
          self.model[0].setQualifier('allowUndefined',True)


  def handleListClicked(self,index):
    #print 'CListView.handleListClicked',index
    #print 'CListView.handleListClicked',index.row()
    pass
     
  def updateShowListButton(self,visible=None,enabled=None):
    #print 'updateListButton',visible,self.listWidget.isVisible(),len(self.model)
    if visible is None:
      visible = self.listWidget.isVisible()
    if visible:
      self.showListButton.setText('Hide list')
      if enabled is None:
        if self.model is None or len(self.model)>1:
          enabled = False
        else:
          enabled = True
    else:
      self.showListButton.setText('Show list')
      enabled = True
    self.showListButton.setEnabled(enabled)

  def setListVisible(self,visible=None):
    #print 'setListVisible',visible,'isVisible',self.listWidget.isVisible(),'model',repr(self.model)
    #if self.model is None or len(self.model)==0:
    #  visible = False
    if self.model is not None and len(self.model)>1:
      visible = True
    elif visible is None:
      visible = not(self.listWidget.isVisible())
    if visible: self.populateListWidget()
    self.listWidget.setVisible(visible)
    if self.editable: self.buttonBox.setVisible(visible)
    self.updateShowListButton(visible=visible)
    

  def isListVisible(self):
    return self.listWidget.isVisible()

  def showListEnabled(self):
    if len(self.model)>1:
      return False
    else:
      return True

  def setEditorVisible(self,visible=None):
    #print 'setEditorVisible',visible,'isVisible',self.editorStack.isVisible(),self.model
    #if self.model is None or len(self.model)==0:
    #  visible = False
    if not hasattr(self,'editorStack'): return
    if visible is None:
      visible = not(self.editorStack.isVisible())
    self.editorStack.setVisible(visible)
    if self.editButton is not None:
      if visible:
        self.editButton.setIcon(QtGui.QIcon(PIXMAPMANAGER().getPixmap('bullet_arrow_down')))
      else:
        self.editButton.setIcon(QtGui.QIcon(PIXMAPMANAGER().getPixmap('bullet_arrow_right')))

      
  def handleRowChange(self,row,indx1=None,force=False):
    #print 'CListView.handleRowChange',self.editItemIndex,self.model.get()
    #print 'CListView.handleRowChange input',row,force
    #import traceback
    #traceback.print_stack(limit=5)
    #self.editor.updateModelFromView()
    if (row == self.editItemIndex) and not force:
      #self.setEditorVisible(True)
      #self.editor.setFocus1(QtCore.Qt.PopupFocusReason,indx1)
      if hasattr(self,'editor'): self.editor.setFocus(QtCore.Qt.PopupFocusReason)
      return
    self.connectDataChanged(False)
    if self.editor.model is not None: self.editor.connectUpdateViewFromModel(False)
    #print 'CListView.handleRowChange calling setModel',repr(self.model[row])
    self.editor.setModel(self.model[row])
    self.editor.updateViewFromModel()
    self.editor.validate()
    #print 'CListView.handleRowChange done calling setModel',self.editor,row,repr(self.model[row]),self.model[row]
    self.setEditorVisible(True)
    self.editItemIndex = row
    self.listWidget.setCurrentRow(self.editItemIndex)
    #self.editor.setFocus1(QtCore.Qt.PopupFocusReason,indx1)
    #print 'handleRowChange setFocus'
    self.editor.setFocus(QtCore.Qt.PopupFocusReason)
    self.connectDataChanged(True)
    self.editor.connectUpdateViewFromModel(True)

  def addLine(self,indx=-1):
    maxLength = self.model.qualifiers('listMaxLength')
    if maxLength is not NotImplemented and maxLength is not None and len(self.model) >= maxLength : return
    
    if indx>=0:
      self.editItemIndex = indx
    else:
      self.editItemIndex = len(self.model)
    #print 'CListView.addLine indx',indx,'editItemIndex',self.editItemIndex,self.model
    self.connectDataChanged(False)

    # Add row to listWidget and make it selected
    self.listWidget.makeItem(indx)
    self.listWidget.setCurrentRow(self.editItemIndex)
    #print 'CListView.addLine count after setCurrentRow',self.listWidget.count()
    # Create item in the model and make it the editor model
    #self.connectDataChanged(False)
    self.model.blockSignals(True)
    obj = self.model.addItem(index=indx)
    #self.connectDataChanged(True)
    self.model.blockSignals(False)
    #print 'CListView.addLine indx,obj',indx,obj,type(obj),self.model,self.editItemIndex,self.listWidget.count()
    self.editor.setModel(obj)
    self.editor.updateViewFromModel()
    self.setEditorVisible(True)
    # Update the new row in the listWidget with value of new model item
    if self.viewMode =='table':
      self.populateListWidget()
    else:
      self.updateListItem()
    
    # Reset the connect to update the listWidget
    print 'addLine setFocus'
    self.editor.setFocus(QtCore.Qt.PopupFocusReason)
    self.connectDataChanged(True)
    self.updateShowListButton()

  def deleteLine(self,indx):
    minLength = self.model.qualifiers('listMinLength')
    if minLength is NotImplemented or minLength is None: minLength=0
    if len(self.model) <= minLength : return
    self.connectDataChanged(False)
    if self.viewMode == 'list': obj = self.listWidget.takeItem(indx)
    # Delete the item in the model
    try:
      obj = self.model.pop(indx)
    except:
      # Likely failed because below listMin
      self.connectDataChanged(True)
      return     
    obj.deleteLater()
    # Determine the new current row
    self.editItemIndex = min( indx,len(self.model)-1)
    # Set the model item on the editor
    if self.editItemIndex>=0:
      self.editor.setModel(self.model[self.editItemIndex])
      self.editor.updateViewFromModel()
    else:
      self.editor.setModel(None)
      self.setEditorVisible(False)
    # Just repopulate table (?is there a more efficient approach)
    if self.viewMode == 'table': self.populateListWidget()
    # Set the selected listWidget row
    self.listWidget.setCurrentRow(self.editItemIndex)
    self.updateShowListButton()
    self.validate()
    # Reset the connect to update the listWidget
    self.connectDataChanged(True)
      
  def handleButtonClick(self,button):
    if button in ['append','Insert before']:          
      if button == 'Insert before':
        indx = self.listWidget.currentRow()
      else:
        indx = -1
      self.addLine(indx)           
    elif button == 'delete':
      #if len(self.model) == 1: return
      row = self.listWidget.currentRow()
      self.deleteLine(row)
    elif button == 'editor':
      self.setEditorVisible()

      
  def populateListWidget(self,resize=False):
    #print 'populateListWidget resize',resize
    if self.model is None: return
    if self.viewMode == 'table':
      self.listWidget.setRowCount(len(self.model))
      for row in range(len(self.model)):
        tableTextItems = self.model[row].getTableTextItems()
        if tableTextItems is not None:
          #print 'populateListWidget tableTextItems',tableTextItems
          for col in range(0,len(tableTextItems)):
            self.listWidget.setItem(row,col,QtGui.QTableWidgetItem(tableTextItems[col]))
      if resize:
        self.listWidget.horizontalHeader().resizeSections(QtGui.QHeaderView.ResizeToContents)
        self.listWidget.horizontalHeader().setResizeMode(QtGui.QHeaderView.Interactive)
    else:
      self.listWidget.blockSignals(True)
      self.listWidget.clear()
      #print 'populateListWidget',self.model.objectName(),len(self.model)
      for n in range(len(self.model)):
        #print 'populateListWidget',n
        try:
          value = self.model[n].getTextItem()
        except Exception as e:
          #print 'CListView.populateListWidget',e
          value = '--'
        #print 'CListView.populateListWidget',self.model.objectName(),n,value
        self.listWidget.addItem(value)
        self.setValidity(n)
      #if getattr(self,'editItemIndex',None) is not None: self.handleRowChange(0,True)
      self.listWidget.blockSignals(False)

  def updateViewFromModel(self):
    #print 'CListWidget.updateViewFromModel'
    self.populateListWidget()
    # Beware no editor in non-editable mode
    try:
      self.editor.updateViewFromModel()
    except:
      pass

  def updateModelFromView(self):
    # The model should be kept in sync at the appropriate times (or
    # we are in poo)
    pass

  def updateListItem(self):
    #print 'CListView.updateListItem',self.editItemIndex,self.model[self.editItemIndex]
    if self.viewMode =='table':
      tableTextItems = self.model[self.editItemIndex].getTableTextItems()
      #print 'CListView.updateListItem tableTextItems',tableTextItems
      for ic in range(0,len(tableTextItems)):
        w = self.listWidget.item(self.editItemIndex,ic)
        #print 'CListView.updateListItem',ic,w,tableTextItems[ic]
        if w is not None: w.setText(tableTextItems[ic])
    else:
      value = self.model[self.editItemIndex].getTextItem()
      listItem = self.listWidget.item(self.editItemIndex)
      #print 'CListView.updateListItem value',self.editItemIndex,value,'listItem',listItem,self.listWidget.count(),repr(self.listWidget)
      if listItem is not None: listItem.setText(value)
    self.setValidity(self.editItemIndex)
    self.validate()
    
  def connectDataChanged(self,mode=True):
    #print 'CListView.connectDataChanged',mode,self.editItemIndex,repr(self.editor.model)
    if self.editor.model is None: return
    if mode:
      self.connect(self.editor.model,QtCore.SIGNAL('dataChanged'),self.updateListItem)
    else:
      self.disconnect(self.editor.model,QtCore.SIGNAL('dataChanged'),self.updateListItem)

  
  def setValidity(self,row,isValid=None):
    if isValid is None:
      validity = self.model[row].validity(self.model[row].get())
      if validity.maxSeverity()>SEVERITY_WARNING:
        isValid = False
      else:
        isValid = True
    #print 'CListView.setValidity',self.model.objectName(),row,isValid
    if isinstance(self.listWidget,QtGui.QListWidget):
      widgetRow = self.listWidget.item(row)
    else:
      widgetRow = None
    if widgetRow is not None:
      if isValid:
        widgetRow.setData(QtCore.Qt.DecorationRole,QtCore.QVariant())
      else:   
        widgetRow.setData(QtCore.Qt.DecorationRole,QtGui.QIcon(PIXMAPMANAGER().getPixmap('error_star')))
  
    # Check overall list validity
    validity = self.model.validity(self.model.__dict__['_value'])
    previous = self.listWidget.property('isValid')
    #print 'CListView.setValidity',previous,validity.maxSeverity()
    if validity.maxSeverity()>SEVERITY_WARNING:
      self.listWidget.setProperty('isValid',False)
    else:
      self.listWidget.setProperty('isValid',True)
    self.listWidget.style().unpolish(self)
    self.listWidget.style().polish(self)
    self.listWidget.update()
  
      
  def getNofLines(self):
    if self.viewMode=='table':
      return self.listWidget.rowCount()
    else:
      return self.listWidget.count()

  def setNofLines(self,nLines):
    if self.viewMode == 'table':
      self.listWidget.setRowCount(nLines)
    else:
      # Add or delete lines to get to NLines
      curNLines = self.listWidget.count()
      #print 'exFrame.setNofLines',curNLines,nLines,self.parent()
      if nLines == curNLines:
        return
      elif nLines > curNLines:
        for ii in range(curNLines,nLines):
          self.addLine()

      elif nLines < curNLines and nLines>=0:
        for ii in range(curNLines,nLines,-1):
          #print 'exFrame.setNofLines deleteLine',ii-1
          self.deleteLine(ii-1)
          
  def getValue(self,index=-1):
    value = []
    if index<0:
      first = 0
      last = self.body.layout().count()
    else:
      first = index
      last = index + 1
    if self.viewMode =='table':
      for row in range(first,last):
        v = {}
        for col in range(self.listWidget.coloumnCount()):
          v[self.tableItems[col]] = str(self.listWidget.item(row,col).text())
        value.append(v)        
    else:
      for row in range(first,last):    
        value.append(str(self.listWidget.item(row).text()))

    return value

  def setValue(self,value=[],index=-1):

     #print 'CListView.setValue',value
     if self.viewMode=='table':
       self.listWidget.setRowCount(len(value))
       for row in range(len(value)):
         tableItemValues = value[row].getTableTextItems()
         for col in range(len(tableItemValues)):
           self.listWidget.setItem(row,col,QtGui.QTableWidgetItem(tableItemValues[col]))
     else:
       self.listWidget.clear()       
       for rowValue in value:
         self.listWidget.addItem(rowValue)
         
  def getMenuDef(self):
    if self.editable:
      menu = ['show_list','clear','copy','paste','help']
    else:
      menu = ['show_list','copy','help']
    if self._stacked: menu.insert(0,'handleStack')
    return menu

  def isWidgetOpen(self):
    return self.listWidget.isVisible()

  def handleHideWidget(self):
    if self.isWidgetOpen():
      self.listWidget.hide()
      self.editorStack.hide()
      for ii in range(self.buttonBox.layout().count()):
        self.buttonBox.layout().itemAt(ii).widget().hide()
    else:
      self.listWidget.show()
      self.editorStack.show()
      for ii in range(self.buttonBox.layout().count()):
        self.buttonBox.layout().itemAt(ii).widget().show()

  def validate(self,isValid=None,reportMessage=True):
    if not getattr(self,'editable',True): return True
    CComplexLineWidget.validate(self,isValid=isValid,reportMessage=reportMessage)    
    editorStack = getattr(self,'editorStack',None)
    if editorStack is not None:  editorStack.currentWidget().validate()
 
        
class CFileListView(CListView):

  def __init__(self,parent=None,model=None,qualifiers={},editorQualifiers={}):
    qualis = { 'editorClassName' : CDataFileView }
    qualis.update(qualifiers)
    CListView.__init__(self,parent,model,qualis,editorQualifiers=editorQualifiers)

class CTreeViewAbstractItemModel(QtCore.QAbstractItemModel):

  def __init__(self,parent,model):
     QtCore.QAbstractItemModel.__init__(self,parent)
     self.rootItem = model
     self.rootItem.setAbstractModelParent(self)
     self._headerData = []
     self.unsetCurrentItem()
     self.itemMap = {}

  def setHeaderData(self,value):
    self._headerData = value
     
  def columnCount(self,parent):
    return len(self._headerData)

  def headerData(self,section,orientation,role):
    if orientation != QtCore.Qt.Horizontal: return QtCore.QVariant()
    value = self._headerData[section].get(role,None)
    if value is None:
      return QtCore.QVariant()
    else:
      return QtCore.QVariant(value)

  def childCount(self):
    return self.rootItem.__len__()

  def child(self,row):
    return self.rootItem.__getitem__(row)

  def data(self, index, role):
    if not index.isValid():
      return None    
    item = index.internalPointer()
    return item.data(index.column(),role)

  def index(self, row, column, parent):
    if row < 0 or column < 0 or row >= self.rowCount(parent) or column >= self.columnCount(parent):
      return QtCore.QModelIndex()
    if not parent.isValid():
      parentItem = self.rootItem
    else:
      parentItem = parent.internalPointer()
    childItem = parentItem.child(row)
    #print 'CProjectModel.index',row, column, parent,childItem.objectPath()
    if childItem:
      return self.createIndex(row, column, childItem)
    else:
      return QtCore.QModelIndex()
    
  def parent(self, index):
    if not index.isValid():
      return QtCore.QModelIndex()
    childItem = index.internalPointer()
    parentItem = childItem.abstractModelParent()
    if parentItem == self.rootItem:
      return QtCore.QModelIndex()
    return self.createIndex(parentItem.row(), 0, parentItem)


  #MN Is something like this needed to avoid identical model items
  #appearing to be different entities
  #def createIndex(self, row, column, item):
  #  itemStr = str(row) + '_' + str(column) + '_' + str(item)
  #  if not itemStr in self.itemMap:
  #    self.itemMap[itemStr] = super(CTreeViewAbstractItemModel,self).createIndex(row,column,item)
  #  return self.itemMap[itemStr]


  def rowCount(self, parent):
    #if parent.column() > 0:
    #  return 0
    if not parent.isValid():
      parentItem = self.rootItem
    else:
      parentItem = parent.internalPointer()
    return parentItem.childCount()

  def row(self):
    return 0

  def unsetCurrentItem(self):
    self.unsetCurrentItem0(self.rootItem)

  def unsetCurrentItem0(self,node):
    for n in range(node.childCount()):
      if node.child(n).__dict__.has_key('currentItem'):
        del node.child(n).__dict__['currentItem']
      self.unsetCurrentItem0(node.child(n))
      
  def setCurrentItem(self,obj):
    if isinstance(obj,QtCore.QModelIndex): obj = obj.internalPointer()
    if obj is None: return
    obj.__dict__['currentItem'] = True

  def currentItem(self):
    rv = self.currentItem0(self.rootItem)
    #print 'CTreeViewAbstractItemModel.currentItem',rv
    return rv

  def currentItem0(self,node):
    if node.childCount() == 0: return None
    for n in range(node.childCount()):
      if node.child(n).__dict__.has_key('currentItem') and node.child(n).__dict__['currentItem']:
        return [node.child(n),n]
    for n in range(node.childCount()):
      rv = self.currentItem0(node.child(n))
      if rv is not None:
        rv.append(n)
        return rv
    return None
        

  def onlyOneItem(self):
    node = self
    while node.childCount() > 0:
      if node.childCount() >1: return False
      node = node.child(0)     
    return True

  def lastItem(self):
    # Find the last item in the tree to be set as the default object in
    # the editor
    if self.childCount() == 0:
      return QtCore.QModelIndex()
    else:
      indx = self.child(self.childCount()-1)
      if indx.childCount()>0:
        return indx.child(indx.childCount()-1)
      else:
        return indx

  def dataObj2ModelIndex(self,dataObj):
    qVar = QtCore.QVariant(str(repr(dataObj)))
    indexList = self.match(self.index(0,0,QtCore.QModelIndex()),QtCore.Qt.UserRole,qVar,1)
    #print 'dataObj2ModelIndex',repr(dataObj),indexList
    if len(indexList)>0: return indexList[0]
    #top = self.index(0,0,QtCore.QModelIndex())
    for ir in range(self.rootItem.childCount()):
      node = self.index(ir,0,QtCore.QModelIndex())
      indexList = self.match( self.index(0,0,node),QtCore.Qt.UserRole,qVar,1)
      #print 'dataObj2ModelIndex',ir,indexList                     
      if len(indexList)>0: return indexList[0]
    return None

  '''
  def removeRows(self, position=0, count=1, parent=QtCore.QModelIndex()):
    node = self.nodeFromIndex(parent)
    self.beginRemoveRows(parent, position, position + count - 1)
    node.childItems.pop(position)
    self.endRemoveRows()
  '''

class CTreeViewTreeView(QtGui.QTreeView):

  def __init__(self,parent):
    QtGui.QTreeView.__init__(self,parent)
    self.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
    self.popupMenu = None
    self.connect(self,QtCore.SIGNAL('rightMousePress'),self.showContextMenu)

  def keyReleaseEvent(self,event):
    #print 'CListViewTableWidget.keyReleaseEvent',event.key(),QtCore.Qt.Key_Delete
    if event.key() == QtCore.Qt.Key_Delete:
      pos = self.mapFromGlobal(self.cursor().pos())-QtCore.QPoint(0,self.header().height())
      modelIndex = self.indexAt(pos)
      #print 'CListViewTableWidget.keyReleaseEvent item',pos,modelIndex
      if modelIndex is None: return
      #print 'CListViewTableWidget.keyReleaseEvent',indices
      self.emit(QtCore.SIGNAL('delete'),modelIndex)
      event.accept()
    else:
      event.ignore()
                
  def mousePressEvent(self,event):
    if event.button() == QtCore.Qt.RightButton:
      self.emit(QtCore.SIGNAL('rightMousePress'),event)
    QtGui.QTreeView.mousePressEvent(self,event)

  def mouseReleaseEvent(self,event):
    if event.button() == QtCore.Qt.LeftButton:
      modelIndex = self.indexAt(event.pos())
      #print 'CTreeViewTreeView emiting leftMouseRelease'
      self.emit(QtCore.SIGNAL('leftMouseRelease'),modelIndex)
    QtGui.QTreeView.mouseReleaseEvent(self,event)


  def showContextMenu(self,event):
    if not self.parent().editable: return
    import functools
    modelIndex = self.indexAt(event.pos())
    dataObject = modelIndex.internalPointer()
    iEditor = 0
    for editorDefn in self.parent().editorDefnList:
      if isinstance(dataObject,editorDefn.get('modelClass')): break
      iEditor += 1       
    #print 'showContextMenu dataObject,iEditor',dataObject,iEditor
    if self.popupMenu is None:
      self.popupMenu = QtGui.QMenu(self)
    else:
      self.popupMenu.clear()
    a = self.popupMenu.addAction('Edit')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('edit'),modelIndex))
    label = self.parent().editorDefnList[iEditor].get('label')
    a = self.popupMenu.addAction('Insert '+label+' above')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),modelIndex,
                                                                  self.parent().editorDefnList[iEditor].get('name'),0))
    a = self.popupMenu.addAction('Insert '+label+' below')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),modelIndex,
                                                                 self.parent().editorDefnList[iEditor].get('name'),1))
    for editorDefn in self.parent().editorDefnList[iEditor+1:]:
      a = self.popupMenu.addAction('Append '+editorDefn.get('label','?'))
      self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('insert'),modelIndex,editorDefn.get('name','?')),-1)  
    a = self.popupMenu.addAction('Delete')
    self.connect(a,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('delete'),modelIndex))
                   
    self.popupMenu.popup(event.globalPos())


class CTreeView(CComplexLineWidget):
  LINE_HEIGHT = 32
  SIDE_BOX = False

  def __init__(self,parent=None,model=None,qualifiers={}):
    import CCP4DataManager
    qualis = {'gridLayout' : True, 'iconName' : 'List' }
    qualis.update(qualifiers)
    #print 'CListView qualis',qualis
    CComplexLineWidget.__init__(self,parent=parent,qualifiers=qualis)
    self.setModel(model)
    editorClosable = False
    self.editorDefnList = qualifiers.get('editors')
    self.columnHeaders = qualifiers.get('columnHeaders')

    # The title/icon line
    self.layout().removeWidget(self.iconButton)
    title = qualis.get('title',None)
    line = QtGui.QHBoxLayout()
    if self.iconButton is not None: line.addWidget(self.iconButton)
    import CCP4GuiUtils
    #self.showListIcons = {}
    #self.showListIcons['enabled'] = CCP4GuiUtils.createIcon(name='ShowList')    
    #self.showListIcons['disabled'] = CCP4GuiUtils.createIcon(name='List_grey')
    self.showListButton = QtGui.QToolButton(self)
    self.showListButton.setText('Show list')
    self.showListButton.setToolTip('Show a list to enable entering more than one item')
    #self.showListButton.setIconSize(QtCore.QSize(ICONBUTTONSIZE,ICONBUTTONSIZE))
    #self.showListButton.setMaximumSize(QtCore.QSize(ICONBUTTONSIZE,ICONBUTTONSIZE))
    self.connect(self.showListButton,QtCore.SIGNAL('released()'),self.setListVisible)
    line.insertWidget(1,self.showListButton)
    if title is not None: line.addWidget(CItalicLabel(title,self))
    line.addStretch(2)
    if CTreeView.SIDE_BOX:
      self.layout().addLayout(line,0,0,1,2)
    else:
      self.layout().addLayout(line,0,0)
    # Put menu on left mouse button (as well as right)
    self.connect(self.iconButton,QtCore.SIGNAL('leftMousePress'),self.updateMenu)
    self.iconButton.setToolTip('Click for menu')

    # The tree widget
    self.abstractModel = CTreeViewAbstractItemModel(self,self.model)
    self.abstractModel.setHeaderData(qualifiers.get('columnHeaders',[]))
    self.listWidget = CTreeViewTreeView(self)
    self.listWidget.setModel(self.abstractModel)
    section=-1
    for column in qualifiers.get('columnHeaders',[]):
      section+=1
      width = column.get('width',None)
      if width is not None: self.listWidget.setColumnWidth(section,width)
    
    if CTreeView.SIDE_BOX:
      self.layout().addWidget(self.listWidget,1,1)
    else:
      self.layout().addWidget(self.listWidget,1,0)

    #self.abstractModel.setCurrentRow()
    self.listWidget.expandAll()
      
    self.editItemIndex = [None]

    # Edit buttons
    if self.editable:
      import functools
      self.buttonBox = QtGui.QFrame(self)
      if CTreeView.SIDE_BOX:
        self.buttonBox.setLayout(QtGui.QVBoxLayout())
      else:
        self.buttonBox.setLayout(QtGui.QHBoxLayout())
      self.buttonBox.layout().setContentsMargins(0,0,0,0)
      editMenu = [['list_add_grey','Add item','append'],['list_delete_grey','Remove item','delete']]
      if editorClosable: editMenu.append(['bullet_arrow_down','Open editor','editor'])
      for item in editMenu:
        icon = QtGui.QIcon(PIXMAPMANAGER().getPixmap(item[0]))
        button = QtGui.QPushButton(self)
        button.setIcon(icon)
        button.setMaximumHeight(ICONBUTTONSIZE)
        button.setMaximumWidth(ICONBUTTONSIZE)
        button.setToolTip(item[1])
        self.buttonBox.layout().addWidget(button)
        
        if item[2] == 'append' and len(self.editorDefnList)>0:
          menu = QtGui.QMenu(self)
          for editorDefn in self.editorDefnList:
            action = menu.addAction('Add '+editorDefn.get('label'))
            action.setObjectName(editorDefn.get('name'))
            self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleButtonClick,'list_add_'+editorDefn.get('name')))
          button.setMenu(menu)
        else:
          self.connect(button,QtCore.SIGNAL('clicked()'),functools.partial(self.handleButtonClick,item[2]))
        
      if not CListView.SIDE_BOX: self.buttonBox.layout().addStretch(1)

      if CTreeView.SIDE_BOX:
        self.layout().addWidget(self.buttonBox,1,0)
      else:
        self.layout().addWidget(self.buttonBox,2,0)
      if editorClosable:
        self.editButton = self.buttonBox.itemAt(2).widget()
        self.editButton.setCheckable(True)
      else:
        self.editButton = None

      # Set up the editors for individual items in a QStackedWidget
      self.editorStack = QtGui.QStackedWidget()
      if CTreeView.SIDE_BOX:
        self.layout().addWidget(self.editorStack,2,0,1,2)
      else:
        self.layout().addWidget(self.editorStack,3,0)
      import CCP4DataManager
      for editorDefn in self.editorDefnList:
        editor = CCP4DataManager.DATAMANAGER().widget(modelClass=editorDefn.get('modelClass'),parentWidget=self.editorStack)
        editor.setObjectName(editorDefn.get('modelClass').__name__)
        #print 'CTreeView.init editor',editorDefn,editor
        self.editorStack.addWidget(editor)
      self.editorStack.setVisible(True)      
      self.handleRowChange(dataObj = self.abstractModel.lastItem())
     
      if self.model is None or self.abstractModel.onlyOneItem():
        self.setListVisible(False)
      else:
        self.setListVisible(True)

      self.connectDataChanged()
      self.connect(self.listWidget,QtCore.SIGNAL('currentRowChanged'),self.handleRowChange)
      self.connect(self.listWidget,QtCore.SIGNAL('edit'),self.handleRowChange)
      self.connect(self.listWidget,QtCore.SIGNAL('insert'),self.addLine)
      self.connect(self.listWidget,QtCore.SIGNAL('delete'),self.deleteLine)
      self.connect(self.listWidget,QtCore.SIGNAL('leftMouseRelease'),self.handleRowChange)
      #self.connect(self.listWidget,QtCore.SIGNAL('itemClicked(QTreeWidgetItem*,int)'),self.handleTreeClicked)


  def resetAbstractModel(self):
    self.abstractModel = CTreeViewAbstractItemModel(self,self.model)
    self.abstractModel.setHeaderData(self.columnHeaders)
    self.listWidget.setModel(self.abstractModel)

      
  def setModel(self,value):
    self.model = value
    if self.model is not None and self.model.__len__() == 0:
      self.model.addItem()
    
    if self.model.qualifiers('listMinLength') is NotImplemented or self.model.qualifiers('listMinLength')==0:
      #print 'CTreeView.setModel setting allowUndefined',self.model.objectPath(),self.model
      self.model[0].setQualifier('allowUndefined',True)

  def setListVisible(self,visible=None):
    #print 'setListVisible',visible,'isVisible',self.listWidget.isVisible(),'model',self.model
    #if self.model is None or len(self.model)==0:
    #  visible = False
    if not self.abstractModel.onlyOneItem():
      visible = True
    elif visible is None:
      visible = not(self.listWidget.isVisible())
    #if visible: self.populateListWidget()
    self.listWidget.setVisible(visible)
    self.buttonBox.setVisible(visible)
    self.editorStack.setCurrentIndex(1)
    '''
    if visible:
      self.editButton.setIcon(QtGui.QIcon(PIXMAPMANAGER().getPixmap('bullet_arrow_down')))
    else:
      self.editButton.setIcon(QtGui.QIcon(PIXMAPMANAGER().getPixmap('bullet_arrow_right')))
    '''
    self.update()
    self.updateShowListButton(visible=visible)

  def isListVisible(self):
    return self.listWidget.isVisible()
      
  def updateShowListButton(self,visible=None,enabled=None):
    #print 'updateListButton',visible,self.listWidget.isVisible(),len(self.model)
    if visible is None:
      visible = self.listWidget.isVisible()
    if visible:
      self.showListButton.setText('Hide list')
      if enabled is None:
        if not self.abstractModel.onlyOneItem():
          enabled = False
        else:
          enabled = True
    else:
      self.showListButton.setText('Show list')
      enabled = True
    self.showListButton.setEnabled(enabled)


  def showListEnabled(self):
    return self.abstractModel.onlyOneItem()
        
    
  def getMenuDef(self):
    if self.editable:
      menu = ['show_list','clear','copy','paste','help']
    else:
      menu = ['show_list','copy','help']
    if self._stacked: menu.insert(0,'handleStack')
    return menu
            
  def setEditorVisible(self,visible=None):
    if visible is None: visible = not(self.editorStack.isVisible())
    self.editorStack.setVisible(visible)
    if self.editButton is not None:
      if visible:
        self.editButton.setIcon(QtGui.QIcon(PIXMAPMANAGER().getPixmap('bullet_arrow_down')))
      else:
        self.editButton.setIcon(QtGui.QIcon(PIXMAPMANAGER().getPixmap('bullet_arrow_right')))


  def setEditor(self,mode,obj=None):
    w = self.editorStack.findChild(QtGui.QWidget,mode)
    if w is not None:
      self.editorStack.setCurrentWidget(w)
      if obj is not None:
        w.setModel(obj)
        w.updateViewFromModel()
      self.setEditorVisible(True)
      w.setFocus(QtCore.Qt.PopupFocusReason)
      w.validate()
    else:
      print 'Error in setEditor for mode:',mode
      print 'traceback.print_stack:'
      import traceback
      traceback.print_stack()
    return w
      
  def handleRowChange(self,modelIndex=None,dataObj=None):
    #import traceback
    #traceback.print_stack(limit=8)
    if modelIndex is not None and modelIndex.isValid():
      dataObj = modelIndex.internalPointer()
    #print 'CTreeView.handleRowChange',modelIndex,dataObj
    self.connectDataChanged(False)
    self.setEditor(dataObj.__class__.__name__,dataObj)
    self.abstractModel.unsetCurrentItem()
    self.abstractModel.setCurrentItem(dataObj)
    self.connectDataChanged(True)

  def addLine(self,modelIndex=None,mode=None,position=-1,currentItem=None):
    # mode is name value of definition in self.editorDefnList
    # modelIndex or currentItem (CTreeAbstractModel.currentItem) are a selected item
    # that a new item will be placed before (position=0) or after (position=1) or
    # new item will be appended if position=-1
    #print 'CTreeView.addLine',modelIndex,mode,position,currentItem
    if mode is None: mode = self.editorDefnList[0].get('name')

    self.connectDataChanged(False)
    #print 'CTreeView.addLine dataObj',modelIndex,mode,repr(dataObj)
    if mode == self.editorDefnList[0].get('name'):
      listObj = self.abstractModel.rootItem
      if position==-1 or (modelIndex is None and currentItem is None):
        insertIndex =listObj.__len__()
        listIndex = -1
      else:
        if modelIndex is not None and modelIndex.isValid():
          dataObj = modelIndex.internalPointer()
        elif currentItem is not None:
          dataObj = currentItem[0]
        insertIndex = dataObj.row()
        insertIndex += position
        if insertIndex >= listObj.__len__():
          listIndex = -1
        else:
          listIndex = insertIndex
      self.abstractModel.beginInsertRows(QtCore.QModelIndex(),insertIndex,insertIndex)
      newDataObj = listObj.addItem(index=listIndex)
      self.abstractModel.endInsertRows()
      # Try getting Qt to understand the newly inserted object has a child too
      newModelIndex = self.abstractModel.index(insertIndex,0,QtCore.QModelIndex())
      self.abstractModel.beginInsertRows(newModelIndex,0,0)
    else:
      dataObj = None
      modelClass = None
      for ediDef in self.editorDefnList:
        if ediDef.get('name') == mode: modelClass = ediDef.get('modelClass')
      # We are going to need modelIndex and dataObj when adding item below the top level
      if modelIndex is None and currentItem is None:
        print 'ERROR in CTreeView.addLine - no  selected model index for insertion point'
        return
      elif modelIndex is not None and modelIndex.isValid():
        dataObj = modelIndex.internalPointer()
      elif currentItem is not None:
        dataObj = currentItem[0]
        modelIndex = self.abstractModel.dataObj2ModelIndex(dataObj)
      if dataObj is None or modelIndex is None:
        print 'ERROR in CTreeView.addLine - no consistent selected model index/ data object for insertion point'
        return
      # If selected item is of the same type as item to be added then go
      # up the heirarchy one rung for the parent
      if isinstance(dataObj,modelClass):
        insertIndex = dataObj.row() + position
        modelIndex = self.abstractModel.parent(modelIndex)
        dataObj = modelIndex.internalPointer()
        listObj = dataObj.childListObject()
        if insertIndex>=len(listObj):
          listIndex = -1
        else:
          listIndex = insertIndex
      else:
        listObj = dataObj.childListObject()
        insertIndex = listObj.childCount()
        listIndex = -1
      self.abstractModel.beginInsertRows(modelIndex,insertIndex,insertIndex)
      newDataObj = listObj.addItem(index=listIndex)
    self.abstractModel.endInsertRows()
    self.listWidget.expandAll()
    
    self.setEditor(newDataObj.__class__.__name__,newDataObj)
    self.abstractModel.unsetCurrentItem()
    self.abstractModel.setCurrentItem(newDataObj)
    
    self.updateShowListButton()
    self.validate()
    self.connectDataChanged(True)

  def deleteLine(self,modelIndex=None,dataObj=None):
    # Delete the item in the model
    if modelIndex is not None and modelIndex.isValid():
      dataObj = modelIndex.internalPointer()
    if dataObj is None: return
    if modelIndex is None:  modelIndex = self.abstractModel.dataObj2ModelIndex(dataObj)
    if modelIndex is None: return
    self.connectDataChanged(False)
    self.listWidget.blockSignals(True)
    listObj = dataObj.parent()
    listIndex = dataObj.row()
    #print 'CTreeView.deleteLine',repr(dataObj),modelIndex,repr(listObj),listIndex
    # If this is the model for the editor need to clear up before deleting it
    # Otherwise risk runtime error trying to access deleted C++ object
    # But if deleting dataObj fails (eg goes below listMinLength) then this is not necessary
    editorModel = None
    for idx in range(self.editorStack.count()):
      if self.editorStack.widget(idx).model == dataObj:
         editorModel = dataObj
         editorIndex = idx
         self.editorStack.widget(idx).setModel(None)
    #print 'deleteLine',repr(dataObj),repr(listObj),listIndex
    self.abstractModel.beginRemoveRows(modelIndex.parent(),listIndex,listIndex)
    try:
      listObj.__delitem__(listIndex)
    except CException as e:
      #print 'deleteLine __delitem__ failed'
      if e.count(code=107):
         QtGui.QMessageBox.warning(self,self.windowTitle(),'Can not delete item\nList must contain at least one item')
      else:
        e.warningMessage(message='Error attempting to delete item',windowTitle='Error editing',parent=self)
      self.abstractModel.endRemoveRows()
      if editorModel is not None: self.editorStack.widget(editorIndex).setModel(editorModel)
    except Exception as e:
       QtGui.QMessageBox.warning(self,self.windowTitle(),'Unknown error attempting to delete item')
    else:
      #print 'deleteLine __delitem__ ok'
      self.abstractModel.endRemoveRows()
      self.listWidget.update()
      if listIndex>=listObj.__len__(): listIndex = listObj.__len__()-1
      if listIndex>=0:
        self.handleRowChange(dataObj=listObj.__getitem__(listIndex))                     
      self.updateShowListButton()
      
    self.connectDataChanged(True)
    self.validate()
    self.listWidget.blockSignals(False)

    #self.handleRowChange()
      
  def handleButtonClick(self,button):
    rv = self.abstractModel.currentItem()
    #print 'handleButtonClick',button,'currentItem',rv
    if button == 'delete':
      if rv is None: return
      self.deleteLine(dataObj=rv[0])
    elif button == 'editor':
      self.setEditorVisible()
    elif button[0:9] == 'list_add_':
      self.addLine(currentItem=rv,mode=button[9:],position=-1)           
      

  def updateViewFromModel(self):
    #self.populateListWidget()
    self.listWidget.update()
    self.editorStack.currentWidget().updateViewFromModel()

  def updateModelFromView(self):
    # The model should be kept in sync at the appropriate times (or
    # we are in poo)
    pass

  def updateEditListItem(self):
    self.listWidget.update()
    self.validate()

  def updateListItem(self):
    self.listWidget.update()  
    
  def connectDataChanged(self,mode=True):
    #print 'CListView.connectDataChanged',mode,self
    editor = self.editorStack.currentWidget()
    if editor.model is None: return
    if mode:
      self.connect(editor.model,QtCore.SIGNAL('dataChanged'),self.updateEditListItem)
    else:
      self.disconnect(editor.model,QtCore.SIGNAL('dataChanged'),self.updateEditListItem)


  def setValidity(self,row,isValid=None):
    return
    if isValid is None:
      validity = self.model[row].validity(self.model[row].get())
      if validity.maxSeverity()>SEVERITY_WARNING:
        isValid = False
      else:
        isValid = True
    #print 'CListView.setValidity',self.model.objectName(),row,validity.report(),isValid
    widgetRow = self.listWidget.item(row)
    if widgetRow is not None:
      if isValid:
        widgetRow.setData(QtCore.Qt.DecorationRole,QtCore.QVariant())
      else:   
        widgetRow.setData(QtCore.Qt.DecorationRole,QtGui.QIcon(PIXMAPMANAGER().getPixmap('error_star')))


  def validate(self,isValid=None,reportMessage=True):
    if not getattr(self,'editable',True): return True
    CComplexLineWidget.validate(self,isValid=isValid,reportMessage=reportMessage)
    editorStack = getattr(self,'editorStack',None)
    if editorStack is not None:  editorStack.currentWidget().validate()

        
  def getValue(self,index=-1):
    print 'CTreeView.getValue doing nothing'
    return []

  def setValue(self,value=[],index=-1):
     print 'CTreeView.setValue doing nothing',value     
   




class CStringView(CViewWidget):

    MODEL_CLASS = CCP4Data.CString

    def __init__(self,parent=None,model=None,qualifiers={}):
      qualis = {}
      qualis.update(qualifiers)
      CViewWidget.__init__(self,parent,qualifiers=qualis)
      
      layout = QtGui.QHBoxLayout()
      layout.setSpacing(CViewWidget.MARGIN)
      layout.setContentsMargins(CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN)

      #print 'CStringView qualis',qualis.get('guiMode',None)

      if qualis.get('editable',True) or qualis.get('guiMode','NotAMultiLineText') == 'multiLine':
        self.mode = 'edit'
        if qualifiers.get('enumerators',[]) or (model is not None and len(model.qualifiers('enumerators'))>0): self.mode = 'combo'
        if qualis.get('modelClass',None) is not None:
          enumerators = CCP4Data.classQualifier(qualis['modelClass'],'enumerators')
          if enumerators is not None and len(enumerators)>0: self.mode = 'combo'
        if qualis.get('guiMode',None) is not None: self.mode =  qualis.get('guiMode')
      else:
        self.mode = 'label'
      #print 'CStringView mode',qualis,self.mode
      if self.mode == 'combo':
        comboQualis = {}
        if qualifiers.has_key('modelClass'):
          #comboQualis.update(qualifiers['modelClass']['qualifiers'])
          comboQualis.update(CCP4Data.classQualifiers(qualis['modelClass']))
        else:
          comboQualis.update(qualifiers)
        #print 'CStringView comboQualis', qualifiers.has_key('qualifiers'),comboQualis
        self.widget = CComboBox(self,qualifiers=comboQualis)
        #self.connect(self.widget,QtCore.SIGNAL('currentIndexChanged(int)'),self.updateModelFromView1)
        # Use the CComboBox signal that covers edit and menu change signals
        self.connect(self.widget,QtCore.SIGNAL('dataChanged'),self.updateModelFromView)
        layout.addWidget(self.widget)
      elif ['radio','multiLineRadio'].count(self.mode):
        if self.mode == 'multiLineRadio':
          layout = QtGui.QVBoxLayout()
          layout.setSpacing(CViewWidget.MARGIN)
          layout.setContentsMargins(CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN)
        labelText = qualis.get('label',NotImplemented)
        if labelText is not NotImplemented and labelText is not None:
          label = QtGui.QLabel(qualis.get('label'),self)
          layout.addWidget(label)
        self.widget = CRadioButtonGroup(self)
        self.connect(self.widget,QtCore.SIGNAL('buttonReleased(int)'),self.updateModelFromView1)
      elif self.mode == 'label':
        labelText = qualis.get('label')
        if labelText is not NotImplemented and labelText is not None:
          label = QtGui.QLabel(labelText,self)
          layout.addWidget(label)
        self.widget = CBoldLabel(parent=self)
        layout.addWidget(self.widget)
      elif self.mode == 'multiLine':
        self.widget =CTextEdit(self)
        layout.addWidget(self.widget)
        self.connect(self.widget,QtCore.SIGNAL('textChanged()'),self.updateModelFromView)
        self.setFocusProxy(self.widget)
      else:
        if model is not None:
          self.widget = CLineEdit(self,qualifiers=model.qualifiers())
        else:
          self.widget = CLineEdit(self )
        if qualis.get('charWidth',None) is not None: self.widget.setCharWidth(qualis['charWidth'])
        #self.connect(self.widget,QtCore.SIGNAL('editingFinished()'),self.updateModelFromView)
        #self.connect(self.widget,QtCore.SIGNAL('enterKeyPressed'),self.updateModelFromView)
        self.connect(self.widget,QtCore.SIGNAL('textEdited(const QString &)'),self.updateModelFromView)
        layout.addWidget(self.widget)
        self.setFocusProxy(self.widget)
      self.setLayout(layout)
    
      if self.mode == 'combo':
        self.populateComboBox(model,modelClass=qualifiers.get('modelClass',None))
      elif ['radio','multiLineRadio'].count(self.mode):
        self.populateRadioButtonBox(model,modelClass=qualifiers.get('modelClass',None))
      elif self.mode == 'label' and model is not None:
        self.widget.setText(str(model.getMenuValue()))
      self.setModel(model)

    def setToolTip(self,tip):
      CViewWidget.setToolTip(self,tip)
      self.widget.setToolTip(tip)

   
    def updateViewFromModel(self):
      #print 'CStringView.updateViewFromModel',self.blockUpdateView
      if self.blockUpdateView: return
      #import traceback
      #traceback.print_stack(limit=10)
      if self.model is None: return
      self.widget.blockSignals(True)
      if self.model is not None and self.model.get() is not None:
        if self.mode == 'label':
          # Beware model may be CInt or CFloat
          text = str(self.model.getMenuValue())
        else:
          text = self.model.get()
        if self.mode == 'combo':
          try:
            indx = self.model.qualifiers('enumerators').index(text)
            #print 'CStringView.updateViewFromModel indx',indx
            self.widget.setValue(self.model.qualifiers('menuText')[indx])
          except:
            self.widget.setValue(text)
        else:
          self.widget.setValue(text)
      else:
        self.widget.setValue(None)
      self.widget.blockSignals(False)
      

    def getWidgetText(self):
      if self.mode == 'combo':
        if self.widget.isEditable():
          value = str(self.widget.currentText())
          try:
            value = self.model.qualifiers('enumerators')[self.model.qualifiers('menuText').index(value)]
          except:
            pass
        else:
          value = self.widget.itemData(self.widget.currentIndex()).toString().__str__()
      elif ['radio','multiLineRadio','multiLine'].count(self.mode):
        value = self.widget.getValue()
      else:
        txt = self.widget.text()
        try:
          value = str(txt)
        except Exception as e:
          value = ''
          print 'ERROR invalid character'
          print e
      return value

    def updateModelFromText(self):
      self.updateModelFromView()

    def updateModelFromView1(self,text):
      self.updateModelFromView()

    def updateModelFromView(self,**kw):
      if self.model is None: return
      #print 'CStringView.updateModelFromView',self.model.objectName(),self.model,self.model.parent(),isinstance(self.model.parent(),CCP4Data.CList)
      #import traceback
      #traceback.print_stack(limit=6)
      value = self.getWidgetText()
      self.connectUpdateViewFromModel(False,propagate=True)
      
      try:
        if isinstance(value,str) and len(value) == 0:
          self.model.set(value=None)
        else:
          #self.model.set(value=self.model.PYTHONTYPE(value))
          self.model.set(value=value)
      except CException as e:
        report=e.report(ifStack=False,mode=2)
        #print 'CStringView updateModelFromView error',value,type(value),report
        self.validate(False,report=report)
      else:
        self.validate()
      self.connectUpdateViewFromModel(True,propagate=True)
      

    def populateComboBox(self,model=None,modelClass=None):
      #print 'populateComboBox',self.model.qualifiers('enumerators'),self.model.qualifiers('menuText')
      if model is not None:
        menuText = model.qualifiers('menuText')
        enumerators = model.qualifiers('enumerators')
      elif modelClass is not None:
        menuText = CCP4Data.classQualifier(modelClass,'menuText')
        enumerators = CCP4Data.classQualifier(modelClass,'enumerators')
      else:
        return
      #MN: Note that the "frozen" (ie. running/run) version of this widget will not have a populate method
      if hasattr(self.widget,'populate'): self.widget.populate(enumerators,menuText)

    def populateRadioButtonBox(self,model=None,modelClass=None):
      if model is not None:
        menuText = model.qualifiers('menuText')
        enumerators = model.qualifiers('enumerators')
      elif modelClass is not None:
        menuText = CCP4Data.classQualifier(modelClass,'menuText')
        enumerators = CCP4Data.classQualifier(modelClass,'enumerators')
      #print 'populateRadioButtonBox',menuText,enumerators
      if self.mode == 'radio':
        for ii in range(0,min(len(enumerators),len(menuText))):
          but = self.widget.addRadioButton(enumerators[ii],menuText[ii])
          #print 'populateRadioButtonBox',but
          self.layout().addWidget(but)
      else:
        for ii in range(0,min(len(enumerators),len(menuText))):
          line = QtGui.QHBoxLayout()
          but = self.widget.addRadioButton(enumerators[ii],menuText[ii])
          #print 'populateRadioButtonBox',but
          line.addWidget(but)
          self.layout().addLayout(line)
          

class CFloatView(CStringView):

    MODEL_CLASS = CCP4Data.CFloat

    def __init__(self,parent=None,model=None,qualifiers={}):
        qualis = qualifiers
        CStringView.__init__(self,parent,model=model,qualifiers=qualis)
        #if self.editable: self.setValidator(qualis)

    def setModel(self,model):
      CViewWidget.setModel(self,model)
      #if self.editable: self.setValidator()

    def setValidator(self,qualifiers={}):
        if self.model is None: return
        minValue=self.model.qualifiers('min')
        maxValue=self.model.qualifiers('max')
        if minValue is None and maxValue is None: return
        validator = CDoubleValidator(bottom=minValue,top=maxValue,parent=self)
        if self.mode in ['combo','edit']:
          self.widget.setValidator(validator)

    def updateModelFromView1(self):
      # This is called after each key input but updating a CFloat value
      # can lead to autocompleting eg '1' to '1.0' which sticks in a zero
      # when the user is typing.  So don't do the model update and trust
      # that the call to updateModelFromView when widget losses focus will
      # update the model
      return
        

class CIntView(CStringView):

    MODEL_CLASS = CCP4Data.CInt
    
    def __init__(self,parent=None,model=None,qualifiers={}):
        #print 'CIntView.__init__',parent,model,qualifiers
        qualis = qualifiers
        CStringView.__init__(self,parent,model=model,qualifiers=qualis)
        #if self.editable: self.setValidator(qualis)

    def setModel(self,model):
      CViewWidget.setModel(self,model)
      #if self.editable: self.setValidator()
      
    def setValidator(self,qualifiers={}):
      if self.model is None: return
      minValue=self.model.qualifiers('min')
      maxValue=self.model.qualifiers('max')   
      validator = QtGui.QIntValidator(self)
      if minValue is not None: validator.setBottom(minValue)
      if maxValue is not None: validator.setTop(maxValue)
      if self.mode in ['combo','edit']:
        self.widget.setValidator(validator)


class CBooleanView(CViewWidget):

    MODEL_CLASS = CCP4Data.CBoolean

    def __init__(self,parent=None,model=None,qualifiers={}):
        #print 'CBooleanView.__init__',model.objectName()
        qualis = qualifiers
        CViewWidget.__init__(self,parent,qualis)
        layout = QtGui.QHBoxLayout()
        layout.setSpacing(CViewWidget.MARGIN)
        layout.setContentsMargins(CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN)
        if self.editable:
          self.widget = CCheckBox(self)
          self.connect(self.widget,QtCore.SIGNAL('stateChanged(int)'),self.updateModelFromView1)
        else:
          self.widget = CCheckBoxUneditable(self)
        layout.addWidget(self.widget)
        self.setLayout(layout)
        if model is not None:
          self.setModel(model)
          self.updateViewFromModel()

    def updateViewFromModel(self):
      #print 'CBooleanView.updateViewFromModel',self.model.objectName(),self.model.get()
      if self.model is not None and self.model.get() is not None:
        self.widget.blockSignals(True)
        self.widget.setChecked( bool(self.model.get()) )
        self.widget.blockSignals(False)

    def updateModelFromView1(self,state):
      self.updateModelFromView()
      
    def updateModelFromView(self):
      #print 'CBooleanView.updateModelFromView',self.model.objectName(),self.widget.isChecked()
      if self.model is None: return
      self.connectUpdateViewFromModel(False)
      self.model.set(value=self.widget.isChecked())
      self.connectUpdateViewFromModel(True)
      
        
class CRangeView(CViewWidget):

   MODEL_CLASS = CCP4Data.CRange

   def __init__(self,parent=None,model=None,qualifiers={}):
     qualis = qualifiers
     CViewWidget.__init__(self,parent,qualifiers=qualis)
     layout = QtGui.QHBoxLayout()
     layout.setSpacing(CViewWidget.MARGIN)
     layout.setContentsMargins(CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN)
     #print 'CRangeView.__init__',model
        
     if isinstance(model,CCP4Data.CIntRange):
       self.widgets['start'] = CIntView(self,model.start,qualifiers=qualis)
       self.widgets['end'] = CIntView(self,model.end,qualifiers=qualis)
     else:
       self.widgets['start'] = CFloatView(self,model.start,qualifiers=qualis)
       self.widgets['end'] = CFloatView(self,model.end,qualifiers=qualis)
       

     layout.addWidget(self.widgets['start'])
     layout.addWidget(QtGui.QLabel('to',self))
     layout.addWidget(self.widgets['end'])
     self.setLayout(layout)

     if model is not None: self.setModel(model)
                      

class CGenericGridView(CViewWidget):

   MODEL_CLASS = None
   ITEMS = []

   def __init__(self,parent=None,model=None,qualifiers={}):
     qualis = qualifiers
     CViewWidget.__init__(self,parent,qualifiers=qualis)
     #print 'CGenericView.__init__',model,model.objectName()
     
     self.setLayout(QtGui.QGridLayout())
     self.layout().setSpacing(CViewWidget.MARGIN)
     self.layout().setContentsMargins(CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN,CViewWidget.MARGIN)
     maxRowLength = 0
     for row in self.getWidgetItems():
       maxRowLength = max(maxRowLength,len(row))
       
     import CCP4DataManager
     iRow = -1
     for rowItems in self.getWidgetItems():
        iRow = iRow + 1
        iCol = -1
        for item in rowItems:
          iCol = iCol + 1
          l = QtGui.QLabel(item,self)
          self.layout().addWidget(l,iRow,iCol)
          iCol = iCol + 1
          #self.widgets[item] =CLineEdit(self)
          try:
            self.widgets[item] =CCP4DataManager.DATAMANAGER().widget(model=model.get(item),parentWidget=self)
          
          except:
            try:
              self.widgets[item] =CStringView(parent=self,model=model.get(item))
            except:
              pass
          
          if self.widgets.has_key(item):
            #print 'CGenericView.__init__',item,self.widgets[item]
            self.connect(self.widgets[item],QtCore.SIGNAL('editingFinished()'),self.updateModelFromView)
            #print 'CGenericGridView',len(rowItems),maxRowLength,item == rowItems[-1]
            if len(rowItems) < maxRowLength and item == rowItems[-1]:
              # Is last item on a short tine so give it the remaining columns
              colSpan = 1 + (maxRowLength-len(rowItems))*2
            else:
              colSpan = 1
            self.layout().addWidget(self.widgets[item],iRow,iCol,1,colSpan)
          else:
            print 'CGenericView.__init__',item,'No widget'
         
     if model is not None: self.setModel(model)

   def updateViewFromModel(self):
     #print 'CGenericGridView.updateViewFromModel'
     if self.model is not None:
       for item in self.widgets.keys():
         self.widgets[item].updateViewFromModel()

   def updateModelFromView(self):
     if self.model is not None:
       for item in self.widgets.keys():
         self.widgets[item].updateModelFromView()

   def getWidgetItems(self):
     return self.__class__.ITEMS
             
class CI2XmlHeaderView(CGenericGridView):

   MODEL_CLASS = CCP4File.CI2XmlHeader
   ITEMS = [ ['function','ccp4iVersion'],
              ['pluginName','pluginVersion'],
              ['pluginTitle'],
              ['userId','creationTime'],
              ['comment'] ]


class CPatchSelectionView(CComplexLineWidget):

  MODEL_CLASS = CCP4Data.CPatchSelection

  def __init__(self,parent=None,model=None,qualifiers={}):
    qualis = {}
    qualis.update(qualifiers)
    qualis['vboxLayout'] = True
    CComplexLineWidget.__init__(self,parent=parent,qualifiers=qualis)
    self.setFrameShape(QtGui.QFrame.Box)
    icon = self.layout().takeAt(0).widget()
    icon.deleteLater()
    line = QtGui.QHBoxLayout()
    lab = QtGui.QLabel('Use control parameters and/or apply command file patches from one of the following:',self)
    # Beware the 'italic' label is used in handlePatchListChanged
    lab.setObjectName('italic')
    line.addWidget(lab)
    self.layout().addLayout(line)
    self.patchLayout= QtGui.QVBoxLayout()
    self.layout().addLayout(self.patchLayout)
    if model is not None: self.setModel(model)
    self.viewWidgetDict = {}
    self.connect(COMFILEPATCHMANAGER(),QtCore.SIGNAL('listChanged'),self.handlePatchListChanged)

  def setModel(self,model):
    if model is not None:   
      self.draw(model)
    CComplexLineWidget.setModel(self,model)

  def draw(self,model):
    for patchName,patchTitle in model.getPatches():
      line = QtGui.QHBoxLayout()
      if self.editable:
        cb = CCheckBox(self)
      else:
        cb = CCheckBoxUneditable(self)
      if patchTitle is not None:
        cb.setText(str(patchTitle))
      else:
        cb.setText(str(patchName))
      cb.setObjectName(str(patchName))
      self.connect(cb,QtCore.SIGNAL('released()'),functools.partial(self.updateModelFromView,str(patchName)))
      but = QtGui.QPushButton('View',self)
      self.connect(but,QtCore.SIGNAL('released()'),functools.partial(self.viewPatch,str(patchName)))
      line.addWidget(cb)
      line.addStretch(1)
      line.addWidget(but)
      self.patchLayout.addLayout(line)
    #self.layout().addStretch(5)

  def updateViewFromModel(self):
    if self.model is None: return
    #print 'CPatchSelectionView.updateViewFromModel',self.model.patch
    for cb in self.findChildren(QtGui.QCheckBox):
      cb.setChecked(False)
    cb = self.findChild(QtGui.QCheckBox,self.model.patch.__str__())
    if cb is not None: cb.setChecked(True)

  def updateModelFromView(self,patchName=None):
    if patchName is not None:
      cb0 = self.findChild(QtGui.QCheckBox,patchName)
      #print 'CPatchSelectionView.updateModelFromView',cb0
      if cb0 is not None and cb0.isChecked():
        for cb in self.findChildren(QtGui.QCheckBox):
          if cb != cb0: cb.setChecked(False)
    if self.model is None: return
    self.connectUpdateViewFromModel(False)
    self.model.patch.unSet()
    for cb in self.findChildren(QtGui.QCheckBox):
      if cb.isChecked(): self.model.patch.set(str(cb.objectName()))
    
    self.connectUpdateViewFromModel(True)

  def viewPatch(self,patchName):
    if self.viewWidgetDict.get(patchName,None) is None:
      import CCP4ComFilePatchManagerGui
      self.viewWidgetDict[patchName] = CCP4ComFilePatchManagerGui.CCreatePatchDialog(self,new=False)
      self.viewWidgetDict[patchName].loadPatch(patchName)
    self.viewWidgetDict[patchName].show()
    self.viewWidgetDict[patchName].raise_()

  def handlePatchListChanged(self):
    # refresh the model CPatchSelection.patchsForTask list 
    self.model.set(self.model.fix({'taskName' : self.model.taskName.__str__(), 'patch' : self.model.patch.__str__() }))

    widgetlist = self.findChildren(QtGui.QWidget)
    for widget in widgetlist:
      if widget.objectName() != 'italic': widget.deleteLater()
    for idx in range(self.patchLayout.count()-1,-1,-1):
      try:
        item = self.patchLayout.takeAt(idx)
        item.layout().deleteLater()
        #print 'handlePatchListChanged deleted',idx
      except:
        print 'CPatchSelectionView.handlePatchListChanged failed',idx

    # redraw
    self.draw(self.model)

class CFollowFromJobView(CComplexLineWidget):
  MODEL_CLASS = CCP4Data.CFollowFromJob
  def __init__(self,parent=None,model=None,qualifiers={}):
    qualis = {'iconName':'job'}
    qualis.update(qualifiers)
    CComplexLineWidget.__init__(self,parent=parent,qualifiers=qualis)
    self.setFrameShape(QtGui.QFrame.Box)
    #self.setLineWidth(2)
    import CCP4ProjectWidget
    self.layout().addWidget(QtGui.QLabel('Use data from job',self))
    self.projectId = qualifiers.get('projectId',None)
    if self.projectId is None:
      self.projectId =self.parentTaskWidget().projectId()

    self.combo = CFinishedJobsCombo(self,self.projectId)
    self.listView=None

    self.force = False
    self.setModel(model)
    self.layout().addWidget(self.combo)
    self.layout().addWidget(QtGui.QLabel('as input below..',self))
    self.layout().addStretch(1)
    
    self.combo.load()
    self.connect(self.combo,QtCore.SIGNAL('currentIndexChanged(int)'),functools.partial(self.updateModelFromView,True))
    self.combo.blockSignals(True)


  def getActionDef(self,name=None):
    #print 'CFollowFromJobView.updateMenu',self,type(self),self.getMenuDef()
    if name == 'paste':
      pasteText = 'Paste'
      mimeData = QTAPPLICATION().clipboard().mimeData()
      for item in mimeData.formats():
        #print 'CFollowFromJobView.updateMenu data:',str(item),mimeData.data(item)
        if str(item).startswith('taskParameters') and str(item)[15:]==self.parentTaskWidget().taskName():
          from lxml import etree
          root = etree.fromstring(str(mimeData.data(item)))
          jobNo = root.find('jobNumber').text
          projectName = root.find('projectName').text
          if root.find('projectId').text == self.parentTaskWidget().projectId():
            pasteText = 'Paste parameters from job '+jobNo
          else:
            pasteText = 'Paste parameters from '+projectName+' '+jobNo
      def e(): return (self.editable and self.clipboardLoaded())   
      return dict (
        text = self.tr(pasteText),
        tip = self.tr('Paste data'),
        enabled = e,
        slot = self.paste
      )
    else:
      return CComplexLineWidget.getActionDef(self,name)



  def dropTypes(self):
    return ['taskParameters_'+self.parentTaskWidget().taskName()]

  def currentJobId(self):
    return self.combo.currentJobId()
  
  def reset(self):
    followFrom = PROJECTSMANAGER().db().getProjectFollowFromJobId(projectId=self.combo.projectId)
    self.model.set(followFrom)

  def getValue(self):
    return self.combo.currentJobId()
 
  def updateViewFromModel(self):
    self.combo.blockSignals(True)
    jobId = self.model.get()
    #print 'CFollowFromJobView.updateViewFromModel',jobId,self.combo.signalsBlocked()
    if jobId is not None:
      self.combo.set(self.model.get())
    else:
      self.combo.set(-1)
    self.combo.blockSignals(False)

  def updateInputFiles(self,jobId,projectId=None,force=False):
    #if self.model.get() is None: return
    #print 'CFollowFromJobView.updateInputFiles',jobId,self.model.get()
    self.parentTaskWidget().emit(QtCore.SIGNAL('followFromJobUpdated'),jobId,projectId)
    
    '''
    container = self.parentTaskWidget().getContainer()
    PROJECTSMANAGER().setInputFileNames(container=container,contextJobId=jobId,
                                            projectId=projectId,force=force)
    fileWidgets = self.parentTaskWidget().findChildren(CDataFileView)
    #print  'CFollowFromJobView.updateViewFromModel',fileWidgets
    for w in fileWidgets:
      #print 'CFollowFromJobView.updateViewFromModel',w.objectName()
      w.updateViewFromModel()
      w.validate()
    '''

  def updateModelFromView(self,updateInputFiles=False):
    jobId = self.combo.currentJobId()
    # combo.currentJobId() return 0=='no selection' -1='More'
    #print 'CFollowFromJobView.updateModelFromView',jobId,'*',type(jobId)
    if jobId is None:
      self.model.unSet()
      if updateInputFiles: self.updateInputFiles(None,force=True)
      return
    elif jobId != 'more':
      self.model.set(jobId)
      if updateInputFiles: self.updateInputFiles(jobId,force=True)
    else:    
      if self.listView is None:
        self.listDialog = QtGui.QDialog(self)
        self.listDialog.setWindowTitle('Use data from job..')
        #self.listDialog.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.listDialog.setLayout(QtGui.QHBoxLayout())
        self.listDialog.layout().setSpacing(0)
        self.listDialog.layout().setContentsMargins(0,0,0,0)
        import CCP4DbApi
        self.listView = CFinishedJobsListWidget(self.listDialog,self.projectId,jobStatus = [CCP4DbApi.JOB_STATUS_FINISHED,CCP4DbApi.JOB_STATUS_INTERRUPTED])
        self.listDialog.layout().addWidget(self.listView)
        #self.listDialog.setModal(True)
        #self.listView.set(self.combo.currentJobId())
        self.connect(self.listView,QtCore.SIGNAL('currentItemChanged(QListWidgetItem *,QListWidgetItem *)'),self.handleListDialog)
      self.combo.blockSignals(True)
      self.combo.set(jobId=self.model.get())
      self.combo.blockSignals(False)
      self.listDialog.show()
      self.listDialog.raise_()
      self.listView.load()
      self.listDialog.setFocus(QtCore.Qt.OtherFocusReason)
      self.listDialog.move(self.mapToGlobal(self.combo.pos()))

  def handleListDialog(self,currentItem,previousItem):
    #jobId = CCP4Data.varToUUID(currentItem.data(QtCore.Qt.UserRole))
    if currentItem is None:
      self.model.unSet()
      return
    text = currentItem.text().__str__()
    jobId = PROJECTSMANAGER().db().getJobId(projectId=self.projectId,jobNumber=text.split()[0])
    #print 'CFollowFromJobView.handleListDialog', jobId
    if jobId is None: return
    self.listDialog.close()
    self.combo.addJob(jobId)
    self.combo.set(jobId)
    self.model.set(jobId)
    self.updateInputFiles(jobId,force=True)

  def acceptDropData(self,textData):
    print 'CFollowFromJob.acceptDropData',textData, textData.count('taskParameters')
    if textData.count('taskParameters') > 0:
      from lxml import etree
      root = etree.fromstring(textData)
      print 'CFollowFromJob.acceptDropData',root.find('taskName').text,self.parentTaskWidget().taskName()
      try:
        if root.find('taskName').text == self.parentTaskWidget().taskName():
          self.parentTaskWidget().loadControlParameters(jobId = root.find('jobId').text)
      except:
        return
    else:
      CComplexLineWidget.acceptDropData(self,textData)
      self.updateInputFiles(jobId=self.model.get(),force=True)


class CFinishedJobsListWidget(QtGui.QListWidget):

  def __init__(self,parent,projectId,jobStatus=[]):
    QtGui.QListWidget.__init__(self,parent)
    self.projectId = projectId
    self.jobStatus = jobStatus
    self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.handleJobFinished)
   
  def load(self):
    self.clear()
    jobInfoList = PROJECTSMANAGER().db().getProjectJobListInfo(projectId=self.projectId,
          jobStatus=self.jobStatus,topLevelOnly=True,
          mode=['jobid','jobnumber','taskname'],order='DESC')
    #print 'CFinishedJobsListWidget.load',len(jobInfoList)
    self.blockSignals(True)
    for jobInfo in jobInfoList:
      title = str(jobInfo['jobnumber'])+' '+TASKMANAGER().getTitle(jobInfo['taskname'])
      #print 'CFinishedJobsListWidget.load',title,jobInfo['jobid']
      item = QtGui.QListWidgetItem(title,self)
      item.setData(QtCore.Qt.UserRole,QtCore.QVariant(jobInfo['jobid']))
      self.addItem(item)
      #self.addItem(title)
    self.blockSignals(False)
    #print 'CFinishedJobsListWidget.load',self.count()

  def handleJobFinished(self,args):
    import CCP4DbApi
    if args['projectId'] != self.projectId or args['status'] not in self.jobStatus: return
    self.addJob(args['jobId'])


  def addJob(self,jobId):
    try:
      jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['jobnumber','taskname'])
      jobNumber = jobInfo['jobnumber']
      taskName = jobInfo['taskname']
    except:
      return
    self.blockSignals(True)
    item = QtGui.QListWidgetItem(str(jobNumber)+' '+TASKMANAGER().getTitle(taskName),self)
    #item.setData(QtCore.Qt.UserRole,QtCore.QVariant(jobId))
    self.insertItem(0,item)
    self.blockSignals(False)
    #self.extras.append({'jobid':jobId,'jobnumber':jobNumber,'taskname':taskName})

  def set(self,jobId):
    row = self.findData(jobId)
    #print 'CFinishedJobsListWidget.set',jobId,row
    if row>=0: self.setCurrentRow(row)
    

  def findData(self,jobId):
    for i in range(self.count()):
      var = self.item(i).data(QtCore.Qt.UserRole)
      itemJobId = CCP4Data.varToUUID(var)
      if itemJobId == jobId: return i
    return -1

  def selectedJobs(self):
    selectedIndices =  self.selectionModel().selectedIndexes()
    #print 'CFinishedJobsListWidget.selectedJobs',selectedIndices
    jobList = []
    for idx in selectedIndices:
      jobId = CCP4Data.varToUUID(self.model().data(idx,QtCore.Qt.UserRole))
      if jobId is not None: jobList.append(jobId)
    return jobList

class CJobTitleView(CStringView):
  STRETCH = 5
  MODEL_CLASS = CCP4Data.CJobTitle
  
      
class CFinishedJobsCombo(QtGui.QComboBox):

  def __init__(self,parent,projectId):
    QtGui.QComboBox.__init__(self,parent)
    self.setEditable(False)
    self.projectId = projectId
    #self.extras = []
    self.more = False
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.handleJobFinished)
    self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobUpdated'),self.handleJobUpdated)


  def showPopup(self):
      '''
      This should replicate method in CComboBox to prevent list view being drawn too short
      '''
      QtGui.QComboBox.showPopup(self)
      if sys.platform != 'darwin' : return
      listViewList = self.findChildren(QtGui.QListView)
      if len(listViewList)>0:
        listViewList[0].setWrapping(True)
        if CComboBox.FONTHEIGHT is None: CComboBox.FONTHEIGHT = listViewList[0].fontMetrics().height()+2
        listViewList[0].setMinimumHeight(self.count()*CComboBox.FONTHEIGHT+5)
        #print 'CComboBox.showPopup height',self.count(),CComboBox.FONTHEIGHT,listViewList[0].height()

  def load(self):
    MAXJOBS = 10
    #print 'CFinishedJobCombo.load'
    import CCP4DbApi
    jobInfoList = PROJECTSMANAGER().db().getProjectJobListInfo(projectId=self.projectId,
          jobStatus=[CCP4DbApi.JOB_STATUS_FINISHED],topLevelOnly=True,maxJobs=MAXJOBS+1,
          mode=['jobid','jobnumber','taskname','jobtitle'],order='DESC')
    #print 'CFinishedJobCombo.load',jobInfoList
    self.blockSignals(True)
    self.clear()
    self.addItem('No',QtCore.QVariant(-1))
    self.insertSeparator(1)
    for item in jobInfoList[0:10]:
      if item['jobtitle'] is not None:
        title = str(item['jobnumber'])+' '+item['jobtitle']
      else:
        title = str(item['jobnumber'])+' '+TASKMANAGER().getTitle(item['taskname'])
      self.addItem(title,QtCore.QVariant(item['jobid']))
    #for item in self.extras:
    #  title = str(item['jobnumber'])+' '+TASKMANAGER().getTitle(item['taskname'])
    #  self.addItem(title,QtCore.QVariant(item['jobid']))
    
    if len(jobInfoList)>MAXJOBS:
      self.insertSeparator(999)
      self.addItem('More jobs..')
      self.more = True
    else:
      self.more = False

    self.blockSignals(False)

  def handleJobFinished(self,args):
    import CCP4DbApi
    if args['projectId'] != self.projectId or args['status']!= CCP4DbApi.JOB_STATUS_FINISHED: return
    self.addJob(args['jobId'])

  def handleJobUpdated(self,args):
    if  args['projectId'] != self.projectId or args.get('key') != 'jobtitle': return
    self.blockSignals(True)
    jobId = self.currentJobId()
    self.load()
    self.set(jobId)
    self.blockSignals(False)

  def addJob(self,jobId,jobNumber=None,taskName=None):
    indx = self.findData(QtCore.QVariant(jobId))
    #print 'CFinishedJobsCombo.addJob',jobId,indx
    if indx>=0: return
    if jobNumber is None or taskName is None:
      try:
        jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['jobnumber','taskname'])
        jobNumber = jobInfo['jobnumber']
        taskName = jobInfo['taskname']
      except:
        return
    title = jobNumber+' '+TASKMANAGER().getTitle(taskName)
    self.blockSignals(True)
    self.insertItem(1,title,QtCore.QVariant(jobId))
    self.blockSignals(False)
    #self.extras.append({'jobid':jobId,'jobnumber':jobNumber,'taskname':taskName})
    
  def set(self,jobId=None,jobNumber=None,taskName=None):
    #print 'CFinishedJobsCombo.set',jobId,jobNumber,taskName
    if jobId is None or jobId==-1:
      indx = 0
    else:
      indx = self.findData(QtCore.QVariant(jobId))
      if indx<0:
        self.addJob(jobId,jobNumber,taskName)
      indx = self.findData(QtCore.QVariant(jobId))
      
    if self.currentIndex() == indx: return
    self.blockSignals(True)
    self.setCurrentIndex(indx)
    self.blockSignals(False)
    #print 'CFinishedJobsCombo.set DONE'

  def currentJobId(self):
    if self.currentIndex() == 0:
      return None
    jobId = CCP4Data.varToUUID(self.itemData(self.currentIndex()))
    #print 'CFinishedJobsCombo.currentJobId',jobId,ok
    if len(jobId) ==0:
      return 'more'
    else:
      return jobId

class CExePathListView(CListView):
  MODEL_CLASS = CCP4File.CExePathList
  def __init__(self,parent=None,model=None,qualifiers={}):
    qualis = { 
               'columnHeaders':['Program name','Use this version'],
               'tableItems' : ['exeName','exePath'],
               'editorClassName' : 'CExePathView'
               }
    qualis.update(qualifiers)
    #print 'CExePathListView.__init__ model',model
    CListView.__init__(self,parent,model=model,qualifiers=qualis)


    
class CExePathView(CComplexLineWidget):
  MODEL_CLASS = CCP4File.CExePath

  def __init__(self,parent=None,model=None,qualifiers={}):
    CComplexLineWidget.__init__(self,parent,qualifiers)
    self.widgets = {}
    #line = QtGui.QHBoxLayout()
    #iconWidgetItem = self.layout().takeAt(0)
    #line.addWidget(iconWidgetItem.widget())
    self.layout().addWidget(QtGui.QLabel('Name',self))
    self.widgets['exeName'] = CStringView(parent=self)
    self.widgets['exeName'].setToolTip('The name of a program')
    self.layout().addWidget(self.widgets['exeName'])
    self.widgets['exeName'].setMaximumWidth(150)
    qualis = { 'jobCombo' : False, 'browseDb' : False , 'toolTip' : 'Select the (non-standard) program to use','autoInfoOnFileImport': False }
    self.widgets['exePath'] =  CDataFileView(parent=self,qualifiers=qualis)
    #self.widgets['exePath'].layout().takeAt(0)
    self.layout().addWidget(self.widgets['exePath'])
    self.setModel(model)

  

class CSearchPathListView(CListView):
  MODEL_CLASS = CCP4File.CSearchPathList
  def __init__(self,parent=None,model=None,qualifiers={}):
    #print 'CSearchPathListView'
    qualis = { 
               'columnHeaders':['Executable','Search path'],
               'tableItems' : ['name','path'],
               'editorClassName' : 'CSearchPathView'
               }
    qualis.update(qualifiers)
    CListView.__init__(self,parent,model=model,qualifiers=qualis)


    

class CSearchPathView(CComplexLineWidget):
  MODEL_CLASS = CCP4File.CSearchPath

  def __init__(self,parent=None,model=None,qualifiers={}):
    qualis = { 'vboxLayout' : True }
    qualis.update(qualifiers)
    CComplexLineWidget.__init__(self,parent,qualis)
    self.widgets = {}
    line = QtGui.QHBoxLayout()
    iconWidgetItem = self.layout().takeAt(0)
    line.addWidget(iconWidgetItem.widget())
    line.addWidget(QtGui.QLabel('Executable name',self))
    self.widgets['name'] = CLineEdit(parent=self)
    line.addWidget(self.widgets['name'])
    #print 'CSearchPathView.init',repr(self.layout())
    self.layout().addLayout(line)
    line = QtGui.QHBoxLayout()
    qualis = { 'jobCombo' : False }
    self.widgets['path'] =  CDataFileView(parent=self,qualifiers=qualis)
    line.addWidget(self.widgets['path'])
    self.layout().addLayout(line)

class CFramelessWarning(QtGui.QDialog):
  def __init__(self,parent=None,message=None,position=None):
    QtGui.QDialog.__init__(self,parent=parent)
    #print 'CFramelessWarning',message
    self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Dialog)
    self.setFocusPolicy(QtCore.Qt.NoFocus)
    self.setLayout(QtGui.QGridLayout())
    self.layout().setSpacing(3)
    self.layout().setContentsMargins(3,3,3,3)
    self.label = QtGui.QLabel(self)
    self.layout().addWidget(self.label,1,0)
    '''
    # Bizarrely the button does not close the widget
    self.button = QtGui.QPushButton('x',self)
    self.button.setMaximumWidth(15)
    self.button.setMaximumHeight(15)
    self.layout().addWidget(self.button,0,10)
    self.connect(self.button,QtCore.SIGNAL('released()'),self.close)
    '''
    self.label.setText(message)
    self.setGeometry(position.x(),position.y(),150,10)
    self.show()
    self.timer = QtCore.QTimer()
    self.timer.setSingleShot(True)
    self.connect(self.timer,QtCore.SIGNAL('timeout()'),self.close)
    self.timer.start(3000)
    
class CJobSelectionCombo(QtGui.QComboBox):
  def __init__(self,parent,projectId=None,ifOneJob=False):
    QtGui.QComboBox.__init__(self,parent=parent)
    self.projectId = None
    if projectId is not None: self.setProjectId(projectId)

  def setProjectId(self,projectId=None):
    #print 'CJobSelectionCombo.setProjectId',projectId
    if projectId is None:
      projectId = self.projectId
      if projectId is None: return
    elif projectId == self.projectId:
      return
    else:
      self.projectId = projectId
    self.clear()

    jobInfoList = PROJECTSMANAGER().db().getProjectJobListInfo(projectId=projectId,topLevelOnly=True,order='ASC',mode=['jobnumber','taskname','jobid'])
    #print 'CJobSelectionCombo.setProjectId jobInfoList',jobInfoList
    for jobInfo in jobInfoList:
      self.addItem(jobInfo['jobnumber']+' '+jobInfo['taskname'],QtCore.QVariant(jobInfo['jobid']))

    #print 'CJobSelectionCombo.setProjectId',self.rootModelIndex(),
    #print 'child',self.rootModelIndex().child(0,0),
    #print 'model',self.rootModelIndex().child(0,0).model()

  def getSelection(self):
    qvar = self.itemData(self.currentIndex())
    jobId=str(qvar.toString())
    #print 'CJobSelectionCombo.getSelection',jobId
    return jobId
    
    
    
class CJobSelectionLineEdit(QtGui.QLineEdit):
    def __init__(self,parent,projectId=None,ifOneJob=False):
      QtGui.QLineEdit.__init__(self,parent)
      self.projectId = projectId
      self.ifOneJob = ifOneJob
      if ifOneJob:
        self.setToolTip("Enter job number")
      else:
        self.setToolTip("Enter list of jobs e.g. '27-29,31'")

    def setProjectId(self,projectId):
      self.projectId = projectId

    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 CGenericDataView(CViewWidget):

  def __init__(self,parent=None,qualifiers={},**kw):
    CViewWidget.__init__(self,parent=parent,qualifiers=qualifiers,**kw)
    self.setLayout(QtGui.QVBoxLayout())
    self.widgets={}
    line = QtGui.QHBoxLayout()
    self.widgets['dataType'] = QtGui.QComboBox(self)
    
  
class CEditFileLabel(QtGui.QDialog):
  # Allow user to edit the job title or file annotation
  # Usually called from CProjectWidget
  def __init__(self,parent=None,fileId=None,jobId=None,position=None,fileLabel=None):
    QtGui.QDialog.__init__(self,parent=parent)
    self.fileId = fileId
    self.jobId = jobId
    #self.edited = False
    self.setLayout(QtGui.QVBoxLayout())
    if fileId is not None:
      fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode=['annotation','jobid','fileclass'])
      import CCP4DataManager
      fileClass = CCP4DataManager.DATAMANAGER().getClass(fileInfo.get('fileclass',''))
      if fileClass is not None:
        fileTypeLabel = fileClass.QUALIFIERS['guiLabel']
      else:
        fileTypeLabel = 'file'
      jobInfo =  PROJECTSMANAGER().db().getJobInfo(jobId=fileInfo.get('jobid',None),mode=['jobnumber','jobtitle'])
      self.layout().addWidget(QtGui.QLabel('Enter label for '+fileTypeLabel+' from job '+ \
                                          str(jobInfo.get('jobnumber',''))+' '+ str(jobInfo.get('jobtitle','')),self ))
      label = fileInfo.get('annotation','')
      
    else:
      jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['jobtitle','jobnumber'])
      self.layout().addWidget(QtGui.QLabel('Enter label for job number '+str(jobInfo.get('jobnumber','')),self))
      label = jobInfo.get('jobtitle','')
    self.edit = QtGui.QLineEdit(self)
    self.edit.setMinimumWidth(300)
    #self.connect(self.edit,QtCore.SIGNAL('textChanged ( const QString &)'),self.handleEdit)
    self.layout().addWidget(self.edit)
    bb = QtGui.QDialogButtonBox(self)
    self.layout().addWidget(bb)
    b = bb.addButton(QtGui.QDialogButtonBox.Save)
    self.connect(b,QtCore.SIGNAL('released()'),self.save)
    b = bb.addButton(QtGui.QDialogButtonBox.Cancel)
    self.connect(b,QtCore.SIGNAL('released()'),self.close)
    if label is not None:
      self.edit.setText(label)
      self.oldLabel = label
    else:
      self.oldLabel = ''
    self.show()

  #def handleEdit(self,text):
  #  self.edited = True

  def save(self):
    import re
    label = str(self.edit.text()).strip()
    label = re.sub('\n',' ',label)
    if label != self.oldLabel:
      #print 'CEditFileLabel.save',label
      if self.fileId is not None:
        PROJECTSMANAGER().db().updateFile(fileId=self.fileId,key='annotation',value=label)
      else:
        PROJECTSMANAGER().db().updateJob(jobId=self.jobId,key='jobtitle',value=label)
    self.close()

class SearchBoxCompleter(QtCore.QObject):
  def __init__(self,parent=None):
    QtCore.QObject.__init__(self,parent)

    self.editor = parent
    self.popup = QtGui.QTreeWidget()
    self.popup.setWindowFlags(QtCore.Qt.Popup);
    self.popup.setFocusPolicy(QtCore.Qt.NoFocus);
    self.popup.setFocusProxy(parent);
    self.popup.setMouseTracking(True);

    self.popup.setColumnCount(1);
    self.popup.setUniformRowHeights(True);
    self.popup.setRootIsDecorated(False);
    self.popup.setEditTriggers(QtGui.QTreeWidget.NoEditTriggers);
    self.popup.setSelectionBehavior(QtGui.QTreeWidget.SelectRows);
    self.popup.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Plain);
    self.popup.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff);
    self.popup.header().hide();

    self.popup.installEventFilter(self);

    self.popup.itemClicked.connect(self.doneCompletion)
    self.hits = []

  def doneCompletion(self,clicked=False):
    self.popup.hide()
    if not clicked and self.editor.text().toUtf8() in self.hits:
      #print "doneCompletion (editor)",self.editor.text()
      QtCore.QMetaObject.invokeMethod(self.editor,"returnPressed")
      self.editor.emit(QtCore.SIGNAL('dataSelected'))
    else:
      item = self.popup.currentItem()
      if item:
        #print "doneCompletion",item.text(0)
        self.editor.setText(item.text(0))
        self.editor.emit(QtCore.SIGNAL('changed'))
        self.popup.hide()
        QtCore.QMetaObject.invokeMethod(self.editor,"returnPressed")
        self.editor.emit(QtCore.SIGNAL('dataSelected'))

  def eventFilter(self,obj, ev):
    if obj != self.popup:
      return False

    if ev.type() == QtCore.QEvent.MouseButtonPress:
        self.popup.hide()
        self.editor.setFocus()
        return True

    if ev.type() == QtCore.QEvent.KeyPress:

        consumed = False;
        key = ev.key();
        if key == QtCore.Qt.Key_Enter or key == QtCore.Qt.Key_Return:
            self.doneCompletion(True)
            consumed = True

        elif key == QtCore.Qt.Key_Escape:
            self.editor.setFocus()
            popup.hide()
            consumed = True

        elif key == QtCore.Qt.Key_Up:
            return False
        elif key == QtCore.Qt.Key_Down:
            return False
        elif key == QtCore.Qt.Key_Home:
            return False
        elif key == QtCore.Qt.Key_End:
            return False
        elif key == QtCore.Qt.Key_PageUp:
            return False
        elif key == QtCore.Qt.Key_PageDown:
            return False

        else:
            self.editor.setFocus()
            self.editor.event(ev)
            #self.popup.hide()
            return True

        return consumed

    return False

  def showCompletion(self, choices):

    if choices.isEmpty():
        return

    pal = self.editor.palette();
    color = pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText);

    self.popup.setUpdatesEnabled(False);
    self.popup.clear();
    for i in range(choices.count()):
        item = QtGui.QTreeWidgetItem();
        item.setText(0, choices[i]);
        self.popup.insertTopLevelItem(i,item)

    self.popup.resizeColumnToContents(0);
    self.popup.adjustSize();
    self.popup.setUpdatesEnabled(True);

    h = self.popup.sizeHintForRow(0) * min(7, choices.count()) + 3;
    self.popup.resize(self.editor.width(), h);

    self.popup.move(self.editor.mapToGlobal(QtCore.QPoint(0, self.editor.height())));
    self.popup.setFocus();
    self.popup.show();
    self.popup.setCurrentItem(self.popup.topLevelItem(0))
    
class CSearchBox(CLineEdit):
  def __init__(self,parent=None):
    CLineEdit.__init__(self,parent)
    self.textChanged.connect(self.doSearch)
    self.completer = SearchBoxCompleter(self)
    self.connect(self,QtCore.SIGNAL('textChanged(const QString &)'),self.doSearch)
    
  def setHitList(self,hitList):
    self.completer.hits = []
    self.completer.hits.extend(hitList)
    
  def doSearch(self,text):
    #print 'doSearch',text
    self.emit(QtCore.SIGNAL('changed'))
    self.completer.popup.hide()
    if len(text)<1:
      return
    choicesl = []
    for ch in self.completer.hits:
      if unicode(text.toUtf8()).lower() in ch.lower() or unicode(text.toUtf8()).lower().replace(' ','-') in ch.lower():
        choicesl.append(ch)
    choices = QtCore.QStringList(choicesl)
    self.completer.showCompletion(choices)

class CTreeItem:
  def __init__(self,parent=None,data={},children=[]):
    #if len(data)>0:
    #  print 'CTreeItem.__init__',self,parent,str(data[QtCore.Qt.DisplayRole].toString())
    self.parent = parent
    self.myData = {}
    if isinstance(data,dict):
      self.myData.update(data)
    else:
      self.myData[QtCore.Qt.DisplayRole] = data
    self.children = []
    for c in children:
      if isinstance(c,(list,tuple)):
        self.appendChild(CTreeItem(self,c[0],c[1]))
      else:
        self.appendChild(CTreeItem(self,c))

  def childCount(self):
    return len(self.children)

  def child(self,row):
    if row>=0 and row<len(self.children):
      return self.children[row]
    else:
      return None

  def appendChild(self,item):
     self.children.append(item)

  def row(self):
    if self.parent is not None:
      return self.parent.children.index(self)
    else:
      return 0
    
  def data(self,role):
    return self.myData.get(role,QtCore.QVariant())

  def findChild(self,role,value):
    if not isinstance(value,QtCore.QVariant): value = QtCore.QVariant(value)
    for child in self.children:
      if child.myData.has_key(role) and child.myData[role] == value:
        return child
    return None

class CTasksModel(QtCore.QAbstractItemModel):

  def __init__(self,parent):
     QtCore.QAbstractItemModel.__init__(self,parent)
     import CCP4TaskManager
     modelAsList = CCP4TaskManager.TASKMANAGER().taskTree(shortTitles=True)
     model = []
     for module in modelAsList:
        taskList = []
        print 'CTasksModel',module
        for name,title in module[2]:
          taskList.append( { QtCore.Qt.DisplayRole: title, QtCore.Qt.UserRole : name } )
        model.append( [ { QtCore.Qt.DisplayRole: module[1], QtCore.Qt.UserRole : module[0] }, taskList ] )
     self.rootItem = CTreeItem(self,'root',model)     

  def columnCount(self,parent):
    return 1

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

  def rowCount(self,index):
    if not index.isValid():
      return self.childCount()
    else:
      return index.internalPointer().childCount()

  def child(self,row):
    return self.rootItem.child(row)

  def data(self, index, role):
    if not index.isValid():
      return None    
    item = index.internalPointer()
    #return item.data(index.column(),role)
    return item.data(role)

  def index(self, row, column, parent):
    if row < 0 or column < 0 or row >= self.rowCount(parent) or column >= self.columnCount(parent):
      return QtCore.QModelIndex()
    if not parent.isValid():
      parentItem = self.rootItem
    else:
      parentItem = parent.internalPointer()
    childItem = parentItem.child(row)
    #print 'CProjectModel.index',row, column, parent,childItem.getName()
    if childItem:
      return self.createIndex(row, column, childItem)
    else:
      return QtCore.QModelIndex()

  def parent(self,index):
    if not index.isValid():
      return QtCore.QModelIndex()
    childItem = index.internalPointer()
    parentItem = childItem.parent
    if parentItem == self.rootItem:
      return QtCore.QModelIndex()
    return self.createIndex(parentItem.row(), 0, parentItem)

class CTreeComboBox(QtGui.QComboBox):
  # Represent a tree in a combobox?
  #from  http://qt.shoutwiki.com/wiki/Implementing_QTreeView_in_QComboBox_using_Qt-_Part_2

  def __init__(self,parent):
    QtGui.QComboBox.__init__(self,parent)
    self.skipNextHide = False
    self.setView(QtGui.QTreeView(self))
    self.view().viewport().installEventFilter(self)
    QtGui.QComboBox.resize(self,200,30)
       

  def eventFilter(self,object,event):
    print 'eventFilter'
    if event.type() == QtCore.QEvent.MouseButtonPress and object == self.view().viewport():
        mouseEvent = QtGui.QMouseEvent(event)
        index = self.view().indexAt(mouseEvent.pos())
        print 'eventFilter',self.view().visualRect(index).contains(mouseEvent.pos())
        if  not self.view().visualRect(index).contains(mouseEvent.pos()):
           self.skipNextHide = True
    return False

  def showPopup(self):
    #self.setRootModelIndex(self.model().rootItem.index())
    print 'showPopup'
    QtGui.QComboBox.showPopup(self)

  def hidePopup(self):
    print 'hidePopup'
    self.setRootModelIndex(self.view().currentIndex().parent())
    self.setCurrentIndex(self.view().currentIndex().row())
    if self.skipNextHide:
      self.skipNextHide = False
    else:
      QtGui.QComboBox.hidePopup(self)
    


