/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
* This file defines classes SKGObjectModelBase.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgobjectmodelbase.h"

#include <KStandardDirs>
#include <KLocalizedString>
#include <KLocale>
#include <KIcon>
#include <KColorScheme>

#include <QApplication>
#include <QMimeData>
#include <QDir>
#include <QColor>

#include "skgtraces.h"
#include "skgdocument.h"
#include "skgnodeobject.h"
#include "skgtransactionmng.h"
#include "skgmainpanel.h"

SKGObjectModelBase::SKGObjectModelBase(SKGDocument* iDocument,
                                       const QString& iTable,
                                       const QString& iWhereClause,
                                       QWidget* iParent,
                                       const QString& iParentAttribute,
                                       bool iResetOnCreation)
    : QAbstractItemModel(iParent), m_isResetRealyNeeded(iResetOnCreation), m_document(iDocument),
      m_whereClause(iWhereClause),
      m_parentAttribute(iParentAttribute), m_refreshBlocked(false)
{
    SKGTRACEINFUNC(1);

    setTable(iTable);

    connect(m_document, SIGNAL(tableModified(QString,int,bool)), this, SLOT(dataModified(QString,int)));
    if (SKGMainPanel::getMainPanel()) {
        connect(SKGMainPanel::getMainPanel(), SIGNAL(currentPageChanged()), this, SLOT(pageChanged()), Qt::QueuedConnection);
    }
}

SKGObjectModelBase::~SKGObjectModelBase()
{
    SKGTRACEINFUNC(1);
    clear();
    m_document = NULL;
}

void SKGObjectModelBase::clear()
{
    SKGTRACEINFUNC(1);
    QHashIterator<int, SKGObjectBase*> i(m_objectsHashTable);
    while (i.hasNext()) {
        i.next();
        SKGObjectBase* val = i.value();
        delete val;
        val = NULL;
    }

    m_listObjects.clear();
    m_parentChildRelations.clear();
    m_childParentRelations.clear();
    m_objectsHashTable.clear();
    m_objectsHashTableRows.clear();
}

int SKGObjectModelBase::getID(const SKGObjectBase* obj, int row) const
{
    if (!m_parentAttribute.isEmpty()) {
        return (obj ? obj->getID() : 0);
    }
    return (obj ? 100 * (obj->getID()) + row : 0);
}

QList<SKGDocument::SKGModelTemplate> SKGObjectModelBase::getSchemas() const
{
    return m_listSchema;
}

void SKGObjectModelBase::setSupportedAttributes(const QStringList& iListAttribute)
{
    SKGTRACEINFUNC(1);
    m_listSupported.clear();
    m_listVisibility.clear();
    m_listSize.clear();

    QStringList l = iListAttribute;
    if (m_listSchema.count()) {
        l += SKGServices::splitCSVLine(m_listSchema.at(0).schema);
    }

    QStringList attributesAvailables;
    QStringList attributesAvailablesTmp;
    if (m_listSchema.count()) {
        attributesAvailablesTmp = SKGServices::splitCSVLine(m_listSchema.at(0).schema);
    }
    int nb = attributesAvailablesTmp.count();
    for (int i = 0; i < nb; ++i) {
        attributesAvailables.push_back(attributesAvailablesTmp.at(i).split('|').at(0));
    }

    nb = l.count();
    for (int i = 0; i < nb; ++i) {
        QStringList values = l.at(i).split('|');
        int nbValues = values.count();
        QString att = values.at(0);

        if (nbValues > 0 && !m_listSupported.contains(att) && attributesAvailables.contains(att)) {
            m_listSupported.push_back(att);
            bool visible = true;
            if (nbValues > 1) {
                visible = (i == 0 || values.at(1) == "Y");    // First column is always visible to support grouping
            }
            m_listVisibility.push_back(visible);
            if (nbValues > 2) {
                m_listSize.push_back(SKGServices::stringToInt(values.at(2)));
            } else {
                m_listSize.push_back(-1);
            }
        }
    }

    m_isResetRealyNeeded = true;
}

bool SKGObjectModelBase::setFilter(const QString& iWhereClause)
{
    if (iWhereClause != m_whereClause) {
        m_isResetRealyNeeded = true;
    }
    m_whereClause = iWhereClause;
    return m_isResetRealyNeeded;
}

void SKGObjectModelBase::setTable(const QString& iTable)
{
    if (iTable != m_table) {
        if (!m_table.isEmpty()) {
            m_isResetRealyNeeded = true;
        }
        m_table = iTable;
        m_realTable = SKGServices::getRealTable(m_table);
        if (m_document) {
            m_listSchema = m_document->getDisplaySchemas(m_realTable);
        }
    }
}

QString SKGObjectModelBase::getTable() const
{
    return m_table;
}

void SKGObjectModelBase::setGroupBy(const QString& iAttribute)
{
    if (iAttribute != m_groupby) {
        m_isResetRealyNeeded = true;
        m_groupby = iAttribute;
    }
}

QString SKGObjectModelBase::getGroupBy() const
{
    return m_groupby;
}

QString SKGObjectModelBase::getParentChildAttribute() const
{
    return m_parentAttribute;
}

QString SKGObjectModelBase::getRealTable() const
{
    return m_realTable;
}

QString SKGObjectModelBase::getWhereClause() const
{
    return m_whereClause;
}

SKGDocument* SKGObjectModelBase::getDocument() const
{
    return m_document;
}

void SKGObjectModelBase::buidCache()
{
    SKGTRACEINFUNC(1);
    m_doctransactionTable = (getRealTable() == "doctransaction");
    m_nodeTable = (getRealTable() == "node");

    // Get std colors
    KColorScheme scheme(QPalette::Normal);
    m_fontNegativeColor = qVariantFromValue(scheme.foreground(KColorScheme::NegativeText).color());
}

bool SKGObjectModelBase::blockRefresh(bool iBlocked)
{
    bool previous = m_refreshBlocked;
    m_refreshBlocked = iBlocked;
    return previous;
}

bool SKGObjectModelBase::isRefreshBlocked()
{
    return m_refreshBlocked;
}

void SKGObjectModelBase::refresh()
{
    if (!m_isResetRealyNeeded || isRefreshBlocked()) {
        return;
    }
    SKGTRACEINFUNC(1);
    SKGTRACEL(1) << "Table:" << getTable() << endl;
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    Q_EMIT beforeReset();
    {
        _SKGTRACEINFUNC(10);
        clear();
        m_listAttibutes.clear();
        m_listAttributeTypes.clear();
        /*beginRemoveRows(QModelIndex(), 0, rowCount(QModelIndex())-1);
        endRemoveRows();*/
    }

    {
        _SKGTRACEINFUNC(10);
        QStringList listAttibutesTmp;
        if (m_document && m_document->getAttributesList(m_table, listAttibutesTmp).isSucceeded()) {
            m_isResetRealyNeeded = false;
            if (listAttibutesTmp.count()) {
                // Filter attributes
                int nb = m_listSupported.count();
                if (nb == 0) {
                    setSupportedAttributes(QStringList());
                    nb = m_listSupported.count();
                }
                for (int i = 0 ; i < nb ; ++i) {
                    QString att = m_listSupported.at(i);
                    if (listAttibutesTmp.contains(att) || att.startsWith(QLatin1String("p_"))) {
                        m_listAttibutes.push_back(att);
                        if (att.startsWith(QLatin1String("t_")) || att.startsWith(QLatin1String("p_"))) {
                            m_listAttributeTypes.push_back(SKGServices::TEXT);
                        } else if (att.startsWith(QLatin1String("f_"))) {
                            m_listAttributeTypes.push_back(SKGServices::FLOAT);
                        } else if (att.startsWith(QLatin1String("i_"))) {
                            m_listAttributeTypes.push_back(SKGServices::INTEGER);
                        } else if (att.startsWith(QLatin1String("d_"))) {
                            m_listAttributeTypes.push_back(SKGServices::DATE);
                        } else {
                            m_listAttributeTypes.push_back(SKGServices::OTHER);
                        }
                    }
                }

                // Remove double
                nb = m_listAttibutes.count();
                for (int i = nb - 1 ; i >= 0 ; --i) {
                    QString att = m_listAttibutes.at(i);
                    if (att.contains("_REAL")) {
                        att.replace("_REAL", "_");
                        int p = m_listAttibutes.indexOf(att);
                        if (p == -1) {
                            att = att.toLower();
                            p = m_listAttibutes.indexOf(att);
                        }
                        if (p != -1) {
                            m_listAttibutes.removeAt(p);
                            m_listAttributeTypes.removeAt(p);
                            if (p < i) {
                                --i;
                            }
                        }
                    }
                }
            }

            // Get objects
            QString wc = m_whereClause;
            if (!m_groupby.isEmpty()) {
                if (wc.isEmpty()) {
                    wc = "1=1";
                }
                if (m_groupby.startsWith(QLatin1String("p_"))) {
                    wc += " ORDER BY (SELECT t_value FROM parameters WHERE t_uuid_parent=" % getTable() % ".id||'-" % getRealTable() % "' AND t_name='" % m_groupby % "')";
                } else {
                    wc += " ORDER BY " % m_groupby;
                }
            }
            if (m_document) {
                m_document->getObjects(m_table, wc , m_listObjects);
            }

            // Initialize object to treat
            QString currentGroup;
            int currentGoupId = 0;
            bool groupByDate = (m_groupby.startsWith(QLatin1String("d_")));
            bool groupByNum = (m_groupby.startsWith(QLatin1String("f_")) || m_groupby.startsWith(QLatin1String("i_")));
            int nb = m_listObjects.count();
            SKGTRACEL(1) << nb << " objects found" << endl;
            for (int t = 0; t < nb; ++t) {
                SKGObjectBase* c = new SKGObjectBase(m_listObjects.at(t));
                int id = getID(c, t);

                int idparent = 0;
                if (m_groupby.isEmpty()) {
                    // Grouping by tree
                    if (!m_parentAttribute.isEmpty()) {
                        int idp = SKGServices::stringToInt(c->getAttribute(m_parentAttribute));
                        if (idp > 0) {
                            idparent = idp;
                        }
                    }
                } else {
                    // Grouping on specified attribute
                    QString att = getAttributeForGrouping(*c, m_groupby);
                    if (groupByDate) {
                        QDate date = SKGServices::stringToTime(att).date();
                        if (date == QDate::currentDate()) {
                            att = i18nc("A group name for grouping by date attibute", "Today");
                        } else if (date > QDate::currentDate()) {
                            // In future
                            if (date < QDate::currentDate().addDays(7)) {
                                att = i18nc("A group name for grouping by date attibute", "Next 7 days");
                            } else if (date < QDate::currentDate().addDays(15)) {
                                att = i18nc("A group name for grouping by date attibute", "Next 15 days");
                            } else if (date < QDate::currentDate().addMonths(1)) {
                                att = i18nc("A group name for grouping by date attibute", "Next month");
                            } else if (date < QDate::currentDate().addMonths(3)) {
                                att = i18nc("A group name for grouping by date attibute", "Next 3 months");
                            } else if (date < QDate::currentDate().addMonths(6)) {
                                att = i18nc("A group name for grouping by date attibute", "Next 6 months");
                            } else if (date < QDate::currentDate().addYears(1)) {
                                att = i18nc("A group name for grouping by date attibute", "Next year");
                            } else if (date < QDate::currentDate().addYears(3)) {
                                att = i18nc("A group name for grouping by date attibute", "Next 3 years");
                            } else {
                                att = i18nc("A group name for grouping by date attibute", "Far away in the future");
                            }
                        } else {
                            // In the past
                            if (date > QDate::currentDate().addDays(-7)) {
                                att = i18nc("A group name for grouping by date attibute", "Last 7 days");
                            } else if (date > QDate::currentDate().addDays(-15)) {
                                att = i18nc("A group name for grouping by date attibute", "Last 15 days");
                            } else if (date > QDate::currentDate().addMonths(-1)) {
                                att = i18nc("A group name for grouping by date attibute", "Last month");
                            } else if (date > QDate::currentDate().addMonths(-3)) {
                                att = i18nc("A group name for grouping by date attibute", "Last 3 months");
                            } else if (date > QDate::currentDate().addMonths(-6)) {
                                att = i18nc("A group name for grouping by date attibute", "Last 6 months");
                            } else if (date > QDate::currentDate().addYears(-1)) {
                                att = i18nc("A group name for grouping by date attibute", "Last year");
                            } else if (date > QDate::currentDate().addYears(-3)) {
                                att = i18nc("A group name for grouping by date attibute", "Last 3 years");
                            } else {
                                att = i18nc("A group name for grouping by date attibute", "Far away in the past");
                            }
                        }
                    } else if (groupByNum) {
                        if (att != QChar(8734)) {
                            double d = SKGServices::stringToDouble(att);
                            if (d > 10000.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "> 10000");
                            } else if (d > 1000.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "> 1000");
                            } else if (d > 100.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "> 100");
                            } else if (d > 10.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "> 10");
                            } else if (d > 0.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "> 0");
                            } else if (d < -10000.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "< -10000");
                            } else if (d < -1000.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "< -1000");
                            } else if (d < -100.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "< -100");
                            } else if (d < -10.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "< -10");
                            } else if (d < 0.0) {
                                att = i18nc("A group name for grouping by numerical attibute", "< 0");
                            } else {
                                att = i18nc("A group name for grouping by numerical attibute", "= 0");
                            }
                        }
                    }
                    if (att != currentGroup || currentGoupId == 0) {
                        // Addition of a new group
                        currentGroup = att;
                        currentGoupId--;
                        SKGObjectBase* group = new SKGObjectBase(m_document, "", currentGoupId);
                        group->setAttribute("t_title", currentGroup);

                        m_childParentRelations.insert(currentGoupId, 0);

                        QList<int> childrensids = m_parentChildRelations.value(0);
                        childrensids.push_back(currentGoupId);

                        m_parentChildRelations.insert(0, childrensids);
                        m_objectsHashTableRows.insert(currentGoupId, childrensids.count() - 1);
                        m_objectsHashTable.insert(currentGoupId, group);
                    }

                    idparent = currentGoupId;
                }

                m_childParentRelations.insert(id, idparent);

                QList<int> childrensids = m_parentChildRelations.value(idparent);
                childrensids.push_back(id);

                m_parentChildRelations.insert(idparent, childrensids);
                m_objectsHashTableRows.insert(id, childrensids.count() - 1);
                m_objectsHashTable.insert(id, c);
            }
        }

        // Build cache
        buidCache();

        {
            SKGTRACEINFUNC(1);
            QAbstractItemModel::reset();
        }
    }
    Q_EMIT afterReset();
    QApplication::restoreOverrideCursor();
}

bool SKGObjectModelBase::hasChildren(const QModelIndex& iParent) const
{
    if (iParent.column() > 0) {
        return false;
    }
    _SKGTRACEINFUNC(10);

    if (iParent.isValid() && m_parentAttribute.isEmpty() && m_groupby.isEmpty()) {
        return false;
    }
    return QAbstractItemModel::hasChildren(iParent);
}

int SKGObjectModelBase::rowCount(const QModelIndex& iParent) const
{
    if (iParent.column() > 0) {
        return 0;
    }
    _SKGTRACEINFUNC(10);

    int idParent = 0;
    if (iParent.isValid()) {
        idParent = iParent.internalId();
    }

    return m_parentChildRelations.value(idParent).count();
}

int SKGObjectModelBase::columnCount(const QModelIndex& iParent) const
{
    Q_UNUSED(iParent);
    return m_listAttibutes.count();
}

QModelIndex SKGObjectModelBase::index(int row, int column, const QModelIndex& iParent) const
{
    if (!hasIndex(row, column, iParent)) {
        return QModelIndex();
    }
    _SKGTRACEINFUNC(10);

    int idParent = 0;
    if (iParent.isValid()) {
        idParent = iParent.internalId();
    }

    int idChild = m_parentChildRelations.value(idParent).at(row);

    // SKGTRACE << table << "-" << idParent << "(" << row << ") ==> " << idChild << endl;
    return (idChild ? createIndex(row, column, idChild) : QModelIndex());
}

QModelIndex SKGObjectModelBase::parent(const QModelIndex& iIndex) const
{
    if (!iIndex.isValid()) {
        return QModelIndex();
    }
    _SKGTRACEINFUNC(10);

    int idChild = 0;
    if (iIndex.isValid()) {
        idChild = iIndex.internalId();
    }

    int idParent = m_childParentRelations.value(idChild);
    int row = m_objectsHashTableRows.value(idParent);
    // SKGTRACE << table << "-" << idChild << "(" << row << ") <== " << idParent << endl;
    return idParent ? createIndex(row, 0, idParent) : QModelIndex();
}

int SKGObjectModelBase::getIndexAttribute(const QString& iAttributeName) const
{
    int output = m_listAttibutes.indexOf(iAttributeName);
    if (output == -1) {
        SKGTRACE << "[" << iAttributeName << "] not found in [" << getRealTable() << "]" << endl;
    }
    return output;
}

QString SKGObjectModelBase::getAttribute(int iIndex) const
{
    return m_listAttibutes.value(iIndex);
}

SKGServices::AttributeType SKGObjectModelBase::getAttributeType(int iIndex) const
{
    return m_listAttributeTypes.value(iIndex);
}

QVariant SKGObjectModelBase::headerData(int section, Qt::Orientation orientation, int role) const
{
    _SKGTRACEINFUNC(10);

    if (orientation == Qt::Horizontal) {
        if (role == Qt::DisplayRole) {
            QString att;
            if (section >= 0 && section < m_listAttibutes.count()) {
                att = m_listAttibutes.at(section);
            } else {
                att = SKGServices::intToString(section);
            }

            return getDocument()->getDisplay(getTable() % '.' % att);
        } else if (role == Qt::UserRole) {
            QString att;
            if (section >= 0 && section < m_listAttibutes.count()) {
                att = m_listAttibutes.at(section);
            } else {
                att = SKGServices::intToString(section);
            }

            int indexAtt = m_listSupported.indexOf(att);

            att = getDocument()->getDisplay(getTable() % '.' % att);


            if (indexAtt >= 0 && indexAtt < m_listVisibility.count()) {
                bool visible = m_listVisibility.at(indexAtt);
                att += QString("|") % (visible ? "Y" : "N");
                if (indexAtt >= 0 && indexAtt < m_listSize.count()) {
                    att += '|' % SKGServices::intToString(m_listSize.at(indexAtt));
                }
            }
            return att;
        } else if (role == Qt::DecorationRole) {
            QString att;
            if (section >= 0 && section < m_listAttibutes.count()) {
                att = m_listAttibutes.at(section);
            } else {
                att = SKGServices::intToString(section);
            }

            return getDocument()->getIcon(getTable() % '.' % att);
        }
    }
    return QVariant();
}

SKGObjectBase SKGObjectModelBase::getObject(const QModelIndex& iIndex) const
{
    SKGObjectBase* obj = getObjectPointer(iIndex);
    SKGObjectBase output;
    if (obj != NULL) {
        output = *obj;
    }
    return output;
}

SKGObjectBase* SKGObjectModelBase::getObjectPointer(const QModelIndex& iIndex) const
{
    _SKGTRACEINFUNC(10);
    // SKGTRACE << "getObject:" << index.internalId() << endl;
    return m_objectsHashTable.value(iIndex.internalId());
}

QVariant SKGObjectModelBase::data(const QModelIndex& iIndex, int iRole) const
{
    if (!iIndex.isValid()) {
        return QVariant();
    }
    _SKGTRACEINFUNC(10);

    switch (iRole) {
    case Qt::BackgroundRole: {
        SKGObjectBase* obj = getObjectPointer(iIndex);
        if (obj->getTable().isEmpty()) {
            // This is a group
            return QApplication::palette().brush(QPalette::Button);
        }
        break;
    }
    case Qt::DisplayRole:
    case Qt::EditRole:
    case Qt::UserRole: {
        SKGObjectBase* obj = getObjectPointer(iIndex);
        QString att = m_listAttibutes.at(iIndex.column());
        if (obj->getTable().isEmpty()) {
            // This is a group
            if (iIndex.column() == 0) {
                if (iRole == Qt::UserRole) {
                    return -obj->getID();   // For sorting
                } else {
                    return i18nc("How to display a grouping title. Here \"title (count)\"", "%1: %2 (%3)",
                                 getDocument()->getDisplay(m_groupby),
                                 obj->getAttribute("t_title"),
                                 rowCount(SKGObjectModelBase::index(iIndex.row(), 0)));
                }
            }
            return "";
        } else {
            QString val;
            if (att.startsWith(QLatin1String("p_"))) {
                // This is a property
                val = obj->getProperty(att.right(att.count() - 2));
            } else {
                // This is a real attribute
                val = obj->getAttribute(att);
            }

            switch (getAttributeType(iIndex.column())) {
            case SKGServices::FLOAT: {
                double dval = SKGServices::stringToDouble(val);
                return dval;
            }
            case SKGServices::INTEGER: {
                return SKGServices::stringToInt(val);
            }
            case SKGServices::DATE: {
                QDate dval = SKGServices::stringToTime(val).date();
                if (iRole == Qt::DisplayRole) {
                    return KGlobal::locale()->formatDate(dval, static_cast<KLocale::DateFormat>(skgbasegui_settings::date_format()));
                } else {
                    return dval;
                }
            }
            default: {
            }
            }

            if (m_doctransactionTable && att == "t_savestep") {
                return "";
            }

            // return val+"("+SKGServices::intToString(rowCount(index))+")";
            return val;
        }
    }

    case Qt::FontRole: {
        SKGObjectBase* obj = getObjectPointer(iIndex);
        // Text color
        if (obj->getTable().isEmpty()) {
            // This is a group
            if (iIndex.column() == 0) {
                QFont f;
                f.setBold(true);

                return qVariantFromValue(f);
            }
            return "";
        }
        break;
    }
    case Qt::TextColorRole: {
        // Text color
        if (getAttributeType(iIndex.column()) == SKGServices::FLOAT) {
            QVariant value_displayed = SKGObjectModelBase::data(iIndex, Qt::UserRole);
            bool ok = false;
            double value_double = value_displayed.toDouble(&ok);
            if (ok && value_double < 0) {
                return m_fontNegativeColor;
            }
        }
        break;
    }
    case Qt::TextAlignmentRole: {
        // Text alignment
        SKGServices::AttributeType attType = getAttributeType(iIndex.column());
        return static_cast<int>(Qt::AlignVCenter | (attType == SKGServices::FLOAT || attType == SKGServices::INTEGER ? Qt::AlignRight : Qt::AlignLeft));
    }
    case Qt::DecorationRole: {
        // Decoration
        SKGObjectBase* obj = getObjectPointer(iIndex);
        QString att = m_listAttibutes.at(iIndex.column());
        if (obj->getTable().isEmpty()) {
            // This is a group
            if (iIndex.column() == 0) {
                return qVariantFromValue(static_cast<QIcon>(KIcon("arrow-right")));
            };
            return "";
        } else {
            if (iIndex.column() == 0 && m_nodeTable) {
                SKGNodeObject node(*obj);
                return qVariantFromValue(node.getIcon());
            } else if (iIndex.column() == 0 && m_doctransactionTable) {
                return qVariantFromValue(static_cast<QIcon>(KIcon(obj->getAttribute("t_mode") == "U" ? "edit-undo" : "edit-redo")));
            } else if (m_doctransactionTable && att == "t_savestep") {
                if (obj->getAttribute("t_savestep") == "Y") {
                    return qVariantFromValue(static_cast<QIcon>(KIcon("document-save")));
                }
            }
        }
        break;
    }
    case Qt::ToolTipRole: {
        // Tooltip
        QString toolTipString;
        SKGObjectBase* obj = getObjectPointer(iIndex);

        if (obj && m_document) {
            if (m_doctransactionTable) {
                QStringList msg;
                QList<SKGDocument::MessageType> type;
                m_document->getMessages(obj->getID(), msg, type);
                int nbMessages = msg.count();
                if (nbMessages) {
                    for (int i = 0; i < nbMessages; ++i) {
                        if (i != 0) {
                            toolTipString += '\n';
                        }
                        toolTipString += msg.at(i);
                    }
                }
            } else if (getAttributeType(iIndex.column()) == SKGServices::DATE) {
                QString att = m_listAttibutes.at(iIndex.column());
                QString val = obj->getAttribute(att);

                QDate dval = SKGServices::stringToTime(val).date();
                QString fancyDate = KGlobal::locale()->formatDate(dval, KLocale::FancyShortDate);
                QString shortDate = KGlobal::locale()->formatDate(dval, KLocale::ShortDate);


                if (shortDate != fancyDate) {
                    toolTipString = shortDate;
                }
            }

            // Add properties
            QStringList props = obj->getProperties();
            if (props.count() > 0 && !toolTipString.isEmpty()) {
                toolTipString += '\n';
            }
            foreach(const QString & prop, props) {
                if (!toolTipString.isEmpty()) {
                    toolTipString += '\n';
                }
                toolTipString += i18nc("To display a property and its value", "%1=%2", prop, obj->getProperty(prop));
            }

            // Add UUID
            IFSKGTRACEL(1) {
                toolTipString += '\n' % obj->getUniqueID();
            }
        }
        return toolTipString;
    }
    case 99: {
        SKGObjectBase* obj = getObjectPointer(iIndex);
        if (obj) {
            return SKGServices::stringToDouble(obj->getAttribute("f_sortorder"));
        }

        break;
    }
    default : {
    }
    }

    return QVariant();
}

Qt::ItemFlags SKGObjectModelBase::flags(const QModelIndex& iIndex) const
{
    _SKGTRACEINFUNC(10);

    Qt::ItemFlags f = QAbstractItemModel::flags(iIndex) | Qt::ItemIsDropEnabled;

    if (iIndex.isValid()) {
        f |= Qt::ItemIsUserCheckable;
    }

    if (m_nodeTable && iIndex.isValid()) {
        f |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
    }

    if (iIndex.isValid()) {
        QString att = m_listAttibutes.at(iIndex.column());
        if (att.toLower() == att || !getDocument()->getRealAttribute(att).isEmpty()) {
            f |= Qt::ItemIsEditable;
        }
        SKGObjectBase* obj = getObjectPointer(iIndex);
        if (obj->getTable().isEmpty()) {
            f = Qt::ItemIsEnabled;
        }
    }

    return f;
}

bool SKGObjectModelBase::setData(const QModelIndex& iIndex, const QVariant& iValue, int iRole)
{
    if (!iIndex.isValid()) {
        return false;
    }

    if (iRole == Qt::EditRole) {
        SKGError err;
        if (m_nodeTable) {
            SKGNodeObject obj(getObject(iIndex));
            QString name = iValue.toString();
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Bookmark update '%1'", name), err);
            err = obj.setName(name);
            IFOKDO(err, obj.save())
        } else {
            SKGObjectBase obj(getObject(iIndex));

            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Update object"), err);
            SKGObjectBase obj2(obj.getDocument(), obj.getRealTable(), obj.getID());  // To be sure this is not a complex object
            QString att = m_listAttibutes.at(iIndex.column());
            err = obj2.setAttribute(att, att.startsWith(QLatin1String("d_")) && iValue.canConvert<QDateTime>() ? SKGServices::dateToSqlString(iValue.toDateTime()) : iValue.toString());
            IFOKDO(err, obj2.save())
        }

        SKGMainPanel::displayErrorMessage(err);
        return !err;
    }
    return QAbstractItemModel::setData(iIndex, iValue, iRole);
}

Qt::DropActions SKGObjectModelBase::supportedDragActions() const
{
    return (m_nodeTable ? Qt::MoveAction : Qt::IgnoreAction);
}

Qt::DropActions SKGObjectModelBase::supportedDropActions() const
{
    return Qt::MoveAction | Qt::LinkAction;
}

QStringList SKGObjectModelBase::mimeTypes() const
{
    QStringList types;
    types << "application/skg." % getRealTable() % ".ids";
    types << "application/data";
    types << "text/uri-list";
    return types;
}

QMimeData* SKGObjectModelBase::mimeData(const QModelIndexList& iIndexes) const
{
    QMimeData* md = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    QString t = getTable();
    foreach(const QModelIndex & idx, iIndexes) {
        if (idx.isValid() && idx.column() == 0) {
            SKGObjectBase obj = getObject(idx);
            t = obj.getRealTable();
            stream << t;
            stream << obj.getID();
        }
    }

    md->setData("application/skg." % t % ".ids", encodedData);
    return md;
}

bool SKGObjectModelBase::dropMimeData(const QMimeData* iData,
                                      Qt::DropAction iAction,
                                      int iRow, int iColumn,
                                      const QModelIndex& iParent)
{
    Q_UNUSED(iRow);
    if (iAction == Qt::IgnoreAction) {
        return true;
    }
    if (!iData || !(iData->hasFormat("application/skg.node.ids") || iData->hasUrls())) {
        return false;    // TODO(Stephane MANKOWSKI): accept all
    }
    if (iColumn > 0) {
        return false;
    }

    SKGError err;
    // Drop files
    if (iData->hasUrls() && iParent.isValid() && getRealTable() != "node") {
        QList<QUrl> urls = iData->urls();
        int nb = urls.count();
        {
            SKGObjectBase obj(getObject(iParent));

            SKGBEGINPROGRESSTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Property creation"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                QString name = i18n("File");
                int idx = 1;
                while (!err && !obj.getProperty(name).isEmpty()) {
                    idx++;
                    name = i18n("File") % " (" % SKGServices::intToString(idx) % ')';
                }
                err = obj.setProperty(name, urls.at(i).toLocalFile(), iAction == Qt::LinkAction ? "" : urls.at(i).toLocalFile());
                IFOKDO(err, getDocument()->stepForward(i + 1))
            }
        }
    } else if (iData->hasFormat("application/skg.node.ids")) {
        // Drop nodes
        QByteArray encodedData = iData->data("application/skg.node.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        QModelIndex parentIndex = iParent;
        SKGNodeObject parentNode;
        if (parentIndex.isValid()) {
            parentNode = getObject(parentIndex);
            if (!parentNode.isFolder()) {
                // The parent is not a directory
                parentNode.getParentNode(parentNode);
                parentIndex = parentIndex.parent();
            }
        }
        {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Move bookmark"), err);

            double min = 0;
            double max = 0;
            if (iRow >= 1) {
                QModelIndex previousIndex = SKGObjectModelBase::index(iRow - 1, 0, parentIndex);
                SKGNodeObject previousObject(getObject(previousIndex));
                min = previousObject.getOrder();
            }

            if (iRow >= rowCount(parentIndex)) {
                max = min + 1;
            } else {
                QModelIndex nextIndex = SKGObjectModelBase::index(iRow, 0, parentIndex);
                SKGNodeObject nextObject(getObject(nextIndex));
                max = nextObject.getOrder();
            }
            if (max <= min) {
                max = min + 1;
            }

            while (!stream.atEnd() && !err) {
                int o_id;
                QString o_table;
                stream >> o_table;
                stream >> o_id;

                // Set parent
                SKGNodeObject child(getDocument(), o_id);
                err = child.load();
                IFOK(err) {
                    if (parentIndex.isValid()) {
                        err = child.setParentNode(parentNode);
                    } else {
                        err = child.removeParentNode();
                    }
                }

                // Set order
                IFOKDO(err, child.setOrder((min + max) / 2.0))

                // Save
                IFOKDO(err, child.save())
            }
        }
    }
    SKGMainPanel::displayErrorMessage(err);
    return !err;
}

void SKGObjectModelBase::pageChanged()
{
    if (m_isResetRealyNeeded) {
        dataModified("", 0);
    }
}

void SKGObjectModelBase::dataModified(const QString& iTableName, int iIdTransaction)
{
    if (getTable() == iTableName || iTableName.isEmpty()) {
        SKGTRACEINFUNC(1);
        SKGTRACEL(1) << "getTable=" << getRealTable() << endl;
        SKGTRACEL(1) << "Parameters=" << iTableName << " , " << iIdTransaction << endl;
        SKGTabPage* page = SKGTabPage::parentTabPage(qobject_cast< QWidget* >(this->QObject::parent()));
        SKGTabPage* cpage = SKGMainPanel::getMainPanel() ? SKGMainPanel::getMainPanel()->currentPage() : NULL;
        if (page != NULL && page != cpage) {
            m_isResetRealyNeeded = true;
            return;
        }

        // Full refresh
        m_isResetRealyNeeded = true;

        // Refresh model
        refresh();
    }
}

QString SKGObjectModelBase::getAttributeForGrouping(const SKGObjectBase& iObject, const QString& iAttribute) const
{
    if (iAttribute.startsWith(QLatin1String("p_"))) {
        // This is a property
        return iObject.getProperty(iAttribute.right(iAttribute.count() - 2));
    } else {
        // This is a real attribute
        return iObject.getAttribute(iAttribute);
    }
}

#include "skgobjectmodelbase.moc"
