# -*- coding: utf-8 -*-

"""model factory and base class for models"""

from PyQt4 import QtCore, QtGui

#******************************************************************************
#
#******************************************************************************
class ModelFactoryContainer(QtCore.QAbstractTableModel):
    """Convenience class to wrap a list of Modelfactories into a
    QAbstractTableModel()
    
    """
    
    def __init__(self, parent, view, modelFactoryClasses, options=None):
            """
            constructor

            @param parent parent view of the container
            @param view parent view that displays the model factories
            @param modelFactoryClasses list containing model factories
            @param options Options instance or None
            
            The model factories will be initialized on by the container.
            """
            QtCore.QAbstractTableModel.__init__(self, parent)
            self.__modelFactoryClasses = modelFactoryClasses
            self.__modelFactories =[i(view, options=options) for i in modelFactoryClasses]
            
            #self.__modelFactoryInstances = []
            #for factory in modelFactoryClasses:
            #    self.__modelFactoryInstances.append(factory(view, options=options))
            self.__options = options
            self.__view = parent
            
    
    def __len__(self):
        """returns the number of factories instances in the container"""
        return len(self.__modelFactories)
    
    
    def __iter__(self):
        """iterates over alls factory instances in the container"""
        return iter(self.__modelFactories)

    
    def __getitem__(self, i):
        """returns the factorys instance at index i"""
        return self.__modelFactories[i]
            
    def modelFactoryClasses(self):
        """returns the list of model factory classses wrapped by the container"""
        return self.__modelFactoryClasses
    
    
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # QAbstractTableModel() methods
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    def rowCount(self, parent):
        """QAbstractTableModel.rowCount() implementation"""
        return len(self)

    def columnCount(self, parent):
        """QAbstractTableModel.columnCount() implementation"""
        return 1

    def hasIndex(self, row, column, parent):
        """QAbstractTableModel.hasIndex() implementation"""
        if row > -1:
            if column > -1:
                if column == 0:
                    if len(self._data) < row:
                        return True
        return False

    def flags(self, index):
        """QAbstractTableModel.flags() implementation"""
        return QtCore.Qt.ItemIsEditable |          \
                    QtCore.Qt.ItemIsSelectable |  \
                    QtCore.Qt.ItemIsEnabled

    
    def data(self, index, role):
        """QAbstractTableModel.data() implementation"""
        v = None
        if role == QtCore.Qt.DisplayRole:
             text = self.__modelFactories[index.row()].displayName()
             return QtCore.QVariant(text)
        
        # display tooltip if text does not fit column width
        # or item is not entirely visible
        # TODO: tololtip popup is not yet 100% accurate
        elif role == QtCore.Qt.ToolTipRole and self.__options.areatips:
            self.__options.areatips.showItemTip(self.__view, index)
                
        return QtCore.QVariant()


#******************************************************************************
#
#******************************************************************************
class ModelFactoryBase(QtCore.QAbstractTableModel):
    """
    Base class for all model factories
    
    This class is used to bundle model classes and provides additional
    information    about model classes. The factory holds any number of model 
    classes a caller may request from the factory. The factory returns the desired
    class in response that the caller may initialize.

    In addition the factory is responsible for providing the names of the model 
    classes. A caller may query for the name of a model class or its display
    name aswell as for the name and display name of the factory itsself.
    Name should be an internal name identifying the model, display name the 
    string that is meant to be shown to the user. You should make shure that
    the display name is generated (i.e. translated) at runtime
    
    The class is implemented as a simple, read only,  QAbstractTableModel() 
    you may insert into any view. 
    """
    
    def __init__(self, view, options=None):
        """
        constructor

        @param view parent view of the factory
        @param options Options instance or None
        """
        QtCore.QAbstractTableModel.__init__(self, view)
        self.__view = view
        self.__options = options
        
    
    def __len__(self):
        """should return the number of models contained in the factory"""
        raise NotImplementedError()

    def __getitem__(self, i):
        """should return the model class at the specified index"""
        raise NotImplementedError()
    
    def name(self):
        """should return the name of the model factory"""
        raise NotImplementedError()

    def displayName(self):
        """should return the dispplay name of the model factory"""
        raise NotImplementedError()
        
    def modelName(self, i):
        """should return the name of the model class at the specified index"""
        raise NotImplementedError()

    def modelDisplayName(self, i):
        """should return the display name of the model class at the specified index"""
        raise NotImplementedError()


    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # QAbstractTableModel() methods
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    def rowCount(self, parent):
        """QAbstractTableModel.rowCout() implementation"""
        return len(self)

    def columnCount(self, parent):
        """QAbstractTableModel.columnCount() implementation"""
        return 1

    def hasIndex(self, row, column, parent):
        """QAbstractTableModel.hasIndex() implementation"""
        if row > -1:
            if column > -1:
                if column == 0:
                    if len(self._data) < row:
                        return True
        return False

    def flags(self, index):
        """QAbstractTableModel.flags() implementation"""
        return QtCore.Qt.ItemIsEditable |          \
                    QtCore.Qt.ItemIsSelectable |  \
                    QtCore.Qt.ItemIsEnabled

    
    def data(self, index, role):
        """QAbstractTableModel.data() implementation"""
        v = None
        if role == QtCore.Qt.DisplayRole:
             text = self.modelDisplayName(index.row())
             return QtCore.QVariant(text)
        
        # display tooltip if text does not fit column width
        # or item is not entirely visible
        # TODO: tololtip popup is not yet 100% accurate
        elif role == QtCore.Qt.ToolTipRole and self.__options.areatips:
            self.__options.areatips.showItemTip(self.__view, index)
                                        
        return QtCore.QVariant()
        
    
#******************************************************************************
#
#******************************************************************************
class ModelBase(QtCore.QAbstractTableModel):
    """
    QAbstractTableModel implementation. Baseclass for all models

    All models should derrive from this class. To set the headers for
    the model initialize any numbers of ModelColumns() and pass them
    to the constructor. The model itsself is populated on demand by calling
    the headers callback with the item in the row container that is about
    to be displayed.

    
    Sample usage:<br>
    <pre>
    class MyModel(ModelBase):
        
        def __init__(self, parent):

            # init a container with headers and some rows
            header = (
                ModelColumn(text='MyColumn', callback=self.onHeader),
                )
            rows = ("foo", "bar", "baz")
        
            ModelBase.__init__(self, parent, header=header, rows=rows)


        # in the callback of the header do whatever 
        # to return the desired text for the cell
        def onHeader(self, row):
            if row == "foo":
                return "bar"
            return row
            
    </pre>
    
    If options are passed to the model each ModelColomns' flags attribute is 
    compared    against the options' flag attribute to see if the column should
    actually be displayed. So assigning a flag of CTF_WHATEVER to the ModelColumn
    will show the ModelColumn only if the appropriate flag is set in the options 
    instance. To enshure a column is always displayed use the CTF_NONE flag.
        
    
    Derrived classes may overwrite the getDelegate() method to return an
    ItemDelegate to a caller.
    """

    def __init__(self, table, header=None, rows=None, options=None):
        """constructor

        @param table table that contains the model
        
        @paran header of the model. Should be a container of 
            ModelColumn() instances and should support __getitem__()
            and __len__() to access each ModelColumn.
        
        @param rows container of items the model provides. The container 
        should support __getitem__() and __len__() for item access.  When an item
        is about to be displayed the callback of the row is is called with the 
        item    as parameter. Return a stringable representation of the item in 
        response.

        @param options Options() instance or None

        """

        QtCore.QAbstractTableModel.__init__(self, table)
        if options:
            self.__columns = [i for i in header if options.flags & i.flags == i.flags]
        else:
            self.__columns = header
        self.__options = options
        self.__rows = rows
        self.__table = table
        table.setVerticalScrollMode(table.ScrollPerItem)
        table.setHorizontalScrollMode(table.ScrollPerItem)
        table.setShowGrid(True)
        table.horizontalHeader().setVisible(True)
        
        # TODO: last col is not stretched on model change
        table.horizontalHeader().setStretchLastSection(True)
                

                
            
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # additional methods
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def getDelegate(self):
        """
        public method to query the model for its QItemDelegate

        @return QItemDelegate instance or None
        """
        return None

       
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # QAbstractTableModel implementations
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def hasIndex(self, row, column):
        """QAbstractTableModel.hasIndex() implementation"""
        if -1 < column <= len(self.__columns):
            if -1 < row < len(self.__rows):
                return True
        return False

    def rowCount(self, parent):
        """QAbstractTableModel.rowCount() implementation"""
        return len(self.__rows)


    def columnCount(self, parent):
        """QAbstractTableModel.columnCount() implementation"""
        return len(self.__columns)


    def headerData(self, section, orientation, role):
        """QAbstractTableModel.headerData() implementation"""
        if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
            return QtCore.QVariant(self.__columns[section].title)
        elif role == QtCore.Qt.WhatsThisRole:
            return QtCore.QVariant(self.__columns[section].doc)
        return QtCore.QVariant()


    def data(self, index, role):
        """QAbstractTableModel.data() implementation"""

        if self.hasIndex(index.row(), index.column()):
            if role == QtCore.Qt.DisplayRole:
                func = self.__columns[index.column()].callback
                return QtCore.QVariant( func(self.__rows[index.row()]) )

            elif role == QtCore.Qt.WhatsThisRole:
                text = self.__columns[index.column()].doc
                return QtCore.QVariant(text)

            # display tooltip if text does not fit column width
            # or item is not entirely visible
            # TODO: tololtip popup is not yet 100% accurate
            elif role == QtCore.Qt.ToolTipRole and self.__options.areatips:
                self.__options.areatips.showItemTip(self.__table, index)
                
        return QtCore.QVariant()


    # drag support
    #
    def supportedDropActions(self):
        """QAbstractTableModel.supportedDropActions() implementation"""
        return QtCore.Qt.CopyAction


    def flags(self, index):
        """QAbstractTableModel.flags() implementation"""
        default = QtCore.QAbstractTableModel.flags(self, index)
        if self.hasIndex(index.row(), index.column()):
            if self.data(index, QtCore.Qt.DisplayRole).toString():
                return default | QtCore.Qt.ItemIsDragEnabled
        return default


    def mimeTypes(self):
        """QAbstractTableModel.mimeTypes() implementation"""
        types = QtCore.QStringList()
        types << "text/plain"
        return types


    def mimeData(self, indexes):
        """QAbstractTableModel.mimeData() implementation"""
        mimeData = QtCore.QMimeData()
        for index in indexes:
            if self.hasIndex(index.row(), index.column()):
                text = self.data(index, QtCore.Qt.DisplayRole).toString()
                mimeData.setText(text)
                break
        return mimeData

#******************************************************************************
#
#******************************************************************************
class ModelColumn(object):
    """Class representing a column in the model"""

    def __init__(self, title="", flags=0, callback=None, doc=""):
        """
        constructor

        @param title callable that returns the header text
        @param flags one or more of the CTF_* flags the column corrosponds to.
        @param callback callback to be called with the contents of the row
        @param doc callable that returns a columns "What's This?" text

        Callables for "title" and "doc" will be called with no arguments and should
                return the desired string.

                """

        self.title = title
        self.callback = callback
        self.doc = doc
        self.flags = flags
