/***************************************************************************
 *   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
 * A tree view with more features.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgtreeview.h"

#include <KSaveFile>
#include <KIcon>
#include <KMenu>
#include <KLocale>
#include <KFileDialog>
#include <KStandardAction>
#include <KTextBrowser>

#include <QDomDocument>
#include <QHeaderView>
#include <QBasicTimer>
#include <QEvent>
#include <QMouseEvent>
#include <QScrollBar>
#include <QApplication>
#include <QPrinter>
#include <QPainter>
#include <QDesktopServices>
#include <QTimer>
#include <QSvgGenerator>
#include <QClipboard>
#include <QTextCodec>
#include <QPushButton>
#include <qtextobject.h>
#include <qtexttable.h>
#include <QTextDocumentWriter>

#include "skgtraces.h"
#include "skgobjectmodelbase.h"
#include "skgservices.h"
#include "skgdocument.h"
#include "skgerror.h"
#include "skgtransactionmng.h"
#include "skgobjectbase.h"
#include "skgmainpanel.h"
#include "skgtableview.h"
#include "skgsortfilterproxymodel.h"

SKGTreeView::SKGTreeView(QWidget* iParent)
    : QTreeView(iParent),
      m_autoResize(true), m_autoResizeDone(false), m_actAutoResize(NULL),
      m_document(NULL), m_textResizable(true),
      m_model(NULL), m_proxyModel(NULL),
      stickH(false), stickV(false)
{
    // Initialize
    setTextElideMode(Qt::ElideMiddle);
    setAutoExpandDelay(300);
    setAnimated(true);

    m_timerDelayedResize.setSingleShot(true);
    connect(&m_timerDelayedResize, SIGNAL(timeout()), this, SLOT(resizeColumnsToContents()), Qt::QueuedConnection);

    m_timerSelectionChanged.setSingleShot(true);
    connect(&m_timerSelectionChanged, SIGNAL(timeout()), this, SIGNAL(selectionChangedDelayed()), Qt::QueuedConnection);

    m_timerScrollSelection.setSingleShot(true);
    connect(&m_timerScrollSelection, SIGNAL(timeout()), this, SLOT(scroolOnSelection()), Qt::QueuedConnection);

    // Menu
    QHeaderView* hori = header();
    hori->setContextMenuPolicy(Qt::CustomContextMenu);
    m_headerMenu = new KMenu(this);

    setContextMenuPolicy(Qt::ActionsContextMenu);
    connect(hori, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)));
    connect(hori, SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(onSortChanged(int,Qt::SortOrder)));

    //
    KAction* actCopy = KStandardAction::copy(this, SLOT(copy()), NULL);
    actCopy->setShortcutConfigurable(false);
    actCopy->setShortcutContext(Qt::WidgetShortcut);
    if (SKGMainPanel::getMainPanel()) {
        SKGMainPanel::getMainPanel()->registerGlobalAction("edit_copy", actCopy);
    }
    insertGlobalAction("edit_copy");

    m_actExpandAll = new KAction(KIcon("format-indent-more"), i18nc("Noun, user action", "Expand all"), this);
    m_actExpandAll->setShortcut(Qt::ALT + Qt::Key_Plus);
    m_actExpandAll->setShortcutConfigurable(false);
    m_actExpandAll->setShortcutContext(Qt::WidgetShortcut);
    connect(m_actExpandAll, SIGNAL(triggered(bool)), this, SLOT(expandAll()));
    if (SKGMainPanel::getMainPanel()) {
        SKGMainPanel::getMainPanel()->registerGlobalAction("edit_expandall", m_actExpandAll);
    }
    insertGlobalAction("edit_expandall");

    m_actCollapseAll = new KAction(KIcon("format-indent-less"), i18nc("Noun, user action", "Collapse all"), this);
    m_actCollapseAll->setShortcut(Qt::ALT + Qt::Key_Minus);
    m_actCollapseAll->setShortcutConfigurable(false);
    m_actCollapseAll->setShortcutContext(Qt::WidgetShortcut);
    connect(m_actCollapseAll, SIGNAL(triggered(bool)), this, SLOT(collapseAll()));
    if (SKGMainPanel::getMainPanel()) {
        SKGMainPanel::getMainPanel()->registerGlobalAction("edit_collapseal", m_actCollapseAll);
    }
    insertGlobalAction("edit_collapseal");

    connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(onActionTriggered(int)));
    connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(onActionTriggered(int)));
    connect(horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onRangeChanged()));
    connect(verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onRangeChanged()));

    // Headers
    hori->setMovable(true);
    hori->setResizeMode(QHeaderView::Fixed);
    setWordWrap(false);

    connect(header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(setupHeaderMenu()), Qt::QueuedConnection);

    connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(onClick(QModelIndex)));
    connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(onCollapse(QModelIndex)));
    connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(onExpand(QModelIndex)));


    QAbstractScrollArea* scrollArea = qobject_cast<QAbstractScrollArea*>(this);
    if (scrollArea) {
        QWidget* vp = scrollArea->viewport();
        if (vp) {
            vp->installEventFilter(this);
            scrollArea->installEventFilter(this);
        }
    }

    // Save original size
    m_fontOriginalPointSize = this->font().pointSize();
    m_iconOriginalSize = this->iconSize().height();
    if (m_iconOriginalSize <= 0) {
        m_iconOriginalSize = 16;
    }
}

SKGTreeView::~SKGTreeView()
{
    m_document = NULL;
    m_headerMenu = NULL;
    m_proxyModel = NULL;
    m_model = NULL;
    m_actExpandAll = NULL;
    m_actCollapseAll = NULL;
}

bool SKGTreeView::isAutoResized()
{
    return m_autoResize;
}

QString SKGTreeView::getState()
{
    SKGTRACEINFUNC(10);
    QDomDocument doc("SKGML");
    QDomElement root = doc.createElement("parameters");
    doc.appendChild(root);

    QHeaderView* hHeader = header();
    if (hHeader && m_model) {
        if (isSortingEnabled()) {
            root.setAttribute("sortOrder", SKGServices::intToString(static_cast<int>(hHeader->sortIndicatorOrder())));
            root.setAttribute("sortColumn", m_model->getAttribute(hHeader->sortIndicatorSection()));

            if (m_proxyModel) {
                root.setAttribute("sortPreviousColumn", SKGServices::intToString(m_proxyModel->getPreviousSortColumn()));
            }
        }
        root.setAttribute("groupBy", m_groupby);

        // Memorize order
        int nb = hHeader->count();
        if (nb) {
            QString columns;
            QString columnsSize;
            QString columnsVisibility;
            for (int i = 0; i < nb; ++i) {
                int idx = hHeader->logicalIndex(i);
                if (i) {
                    columns += ';';
                }
                columns += m_model->getAttribute(idx);

                if (i) {
                    columnsSize += ';';
                }
                columnsSize += SKGServices::intToString(hHeader->sectionSize(idx));

                if (i) {
                    columnsVisibility += ';';
                }
                columnsVisibility += (hHeader->isSectionHidden(idx) ? "N" : "Y");
            }
            root.setAttribute("columns", columns);
            if (!m_autoResize) {
                root.setAttribute("columnsSize", columnsSize);
            }
            root.setAttribute("columnsVisibility", columnsVisibility);
            root.setAttribute("columnsAutoResize", m_autoResize ? "Y" : "N");
        }

        // Memorize expanded groups
        if (!m_groupby.isEmpty()) {
            root.setAttribute("expandedGroups", SKGServices::stringsToCsv(m_expandedNodes));
        }
    }
    root.setAttribute("alternatingRowColors", alternatingRowColors() ? "Y" : "N");
    root.setAttribute("zoomPosition", SKGServices::intToString(zoomPosition()));

    QScrollBar* scroll2 = horizontalScrollBar();
    if (scroll2 && scroll2->value() == scroll2->maximum() && scroll2->value() != scroll2->minimum()) {
        root.setAttribute("stickH", "Y");
    }
    scroll2 = verticalScrollBar();
    if (scroll2 && scroll2->value() == scroll2->maximum() && scroll2->value() != scroll2->minimum()) {
        root.setAttribute("stickV", "Y");
    }
    return doc.toString();
}

void SKGTreeView::setState(const QString& iState)
{
    SKGTRACEINFUNC(10);
    resetColumnsOrder();

    QDomDocument doc("SKGML");

    QString viewState = iState;
    if (viewState.isEmpty() && m_document) {
        // Get default state
        viewState = m_document->getParameter(m_parameterName);
    }

    Qt::SortOrder qtsortorder = Qt::AscendingOrder;
    if (doc.setContent(viewState)) {
        QDomElement root = doc.documentElement();

        QString sortOrder = root.attribute("sortOrder");
        QString sortColumn = root.attribute("sortColumn");
        QString sortPreviousColumn = root.attribute("sortPreviousColumn");
        m_groupby = root.attribute("groupBy");
        QString columns = root.attribute("columns");
        QString columnsSize = root.attribute("columnsSize");
        QString columnsVisibility = root.attribute("columnsVisibility");
        QString columnsAutoResize = root.attribute("columnsAutoResize");
        QString alternatingRowColors2 = root.attribute("alternatingRowColors");
        QString zoomPositionString = root.attribute("zoomPosition");
        QString expandedGroups = root.attribute("expandedGroups");
        stickH = (root.attribute("stickH") == "Y");
        stickV = (root.attribute("stickV") == "Y");

        // Set column order
        QStringList listAtt;
        if (!columns.isEmpty()) {
            listAtt = SKGServices::splitCSVLine(columns, ';');
            QStringList sizes = SKGServices::splitCSVLine(columnsSize, ';');
            QStringList visibilities = SKGServices::splitCSVLine(columnsVisibility, ';');

            int nb = listAtt.count();
            int nbvisibilities = visibilities.count();
            int nbsizes = sizes.count();
            for (int i = 0; i < nb; ++i) {
                if (nbvisibilities == nb) {
                    listAtt[i] = listAtt[i] % '|' % visibilities[i];
                    if (nbsizes == nb) {
                        listAtt[i] = listAtt[i] % '|' % sizes[i];
                    }
                }
            }
        }
        if (m_model) {
            m_model->setSupportedAttributes(listAtt);
        }

        // Set autoResize
        if (!columnsAutoResize.isEmpty()) {
            m_autoResize = (columnsAutoResize == "Y");
            header()->setResizeMode(m_autoResize ? QHeaderView::Fixed : QHeaderView::Interactive);
            if (!m_autoResize) {
                m_timerDelayedResize.stop();
                m_autoResizeDone = false;
            }
        }

        // Set sort
        if (m_proxyModel && !sortPreviousColumn.isEmpty()) {
            m_proxyModel->setPreviousSortColumn(SKGServices::stringToInt(sortPreviousColumn));
        }
        if (m_model && isSortingEnabled() && !sortOrder.isEmpty() && !sortColumn.isEmpty()) {
            int index = SKGServices::splitCSVLine(columns, ';').indexOf(sortColumn);
            if (index == -1) {
                index = m_model->getIndexAttribute(sortColumn);
            }
            if (index == -1) {
                index = 0;
            }
            qtsortorder = static_cast<Qt::SortOrder>(SKGServices::stringToInt(sortOrder));
            this->sortByColumn(index, qtsortorder);
        }

        if (m_model) {
            QString att = m_groupby;
            if (att == "#") {
                att = sortColumn;
            }
            m_model->setGroupBy(att);
            m_model->dataModified();

            refreshExpandCollapse();
        }

        // Set alternatingRowColors
        if (!alternatingRowColors2.isEmpty()) {
            setAlternatingRowColors(alternatingRowColors2 == "Y");
        }
        if (!zoomPositionString.isEmpty()) {
            setZoomPosition(SKGServices::stringToInt(zoomPositionString));
        }

        // Set expanded groups
        m_expandedNodes = SKGServices::splitCSVLine(expandedGroups);
        resetSelection();
    } else {
        if (m_model) {
            m_model->setSupportedAttributes(QStringList());
            m_groupby = "";
            m_model->setGroupBy(m_groupby);
            m_model->dataModified();

            refreshExpandCollapse();
        }

        this->sortByColumn(0, qtsortorder);
    }
}

void SKGTreeView::refreshExpandCollapse()
{
    bool treeMode = !m_model->getParentChildAttribute().isEmpty();
    setRootIsDecorated(treeMode && m_groupby.isEmpty());
    if (m_actExpandAll) {
        m_actExpandAll->setVisible(treeMode || !m_groupby.isEmpty());
    }
    if (m_actCollapseAll) {
        m_actCollapseAll->setVisible(treeMode || !m_groupby.isEmpty());
    }
}

void SKGTreeView::onRangeChanged()
{
    QScrollBar* scroll2 = qobject_cast<QScrollBar*>(sender());
    if ((stickH && scroll2 == horizontalScrollBar()) || (stickV && scroll2 == verticalScrollBar())) {
        scroll2->setValue(scroll2->maximum());
    }
}

void SKGTreeView::onActionTriggered(int action)
{
    QScrollBar* scroll2 = qobject_cast<QScrollBar*>(sender());
    if (scroll2 && action == QAbstractSlider::SliderToMaximum) {
        if (scroll2 == horizontalScrollBar()) {
            stickH = true;
        }
        if (scroll2 == verticalScrollBar()) {
            stickV = true;
        }
    } else {
        if (scroll2 == horizontalScrollBar()) {
            stickH = false;
        }
        if (scroll2 == verticalScrollBar()) {
            stickV = false;
        }
    }
}

void SKGTreeView::insertGlobalAction(const QString& iRegisteredAction)
{
    if (iRegisteredAction.isEmpty()) {
        QAction* sep = new QAction(this);
        sep->setSeparator(true);
        this->insertAction(0, sep);
    } else if (SKGMainPanel::getMainPanel()) {
        QAction* act = SKGMainPanel::getMainPanel()->getGlobalAction(iRegisteredAction);
        this->insertAction(0, act);
    }
}

void SKGTreeView::resizeColumnsToContentsDelayed()
{
    SKGTRACEINFUNC(10);
    m_timerDelayedResize.start(300);
}

void SKGTreeView::resizeColumnsToContents()
{
    SKGTRACEINFUNC(10);
    int nb = header()->count();
    for (int i = nb - 1; i >= 0; --i) {
        resizeColumnToContents(i);
    }
}

void SKGTreeView::showHeaderMenu()
{
    showHeaderMenu(header()->mapFromGlobal(QCursor::pos()));
}

void SKGTreeView::showHeaderMenu(const QPoint& iPos)
{
    if (m_headerMenu) {
        m_headerMenu->popup(header()->mapToGlobal(iPos));
    }
}

void SKGTreeView::setDefaultSaveParameters(SKGDocument* iDocument, const QString& iParameterName)
{
    m_document = iDocument;
    m_parameterName = iParameterName;
}

void SKGTreeView::setupHeaderMenu()
{
    SKGTRACEINFUNC(10);
    if (m_model && m_model->isRefreshBlocked()) {
        return;
    }

    setCornerWidget(NULL);
    if (m_model && m_headerMenu) {
        // Corner button on tables to show the contextual menu
        QPushButton* btn = new QPushButton(this);
        btn->setIcon(KIcon("configure"));
        connect(btn, SIGNAL(clicked()), this, SLOT(showHeaderMenu()));
        setCornerWidget(btn);

        m_headerMenu->clear();

        // Processing
        QMenu* columns = m_headerMenu->addMenu(i18nc("Noun, Menu name", "Columns"));

        // Get current groupby column
        QMenu* groupbymenu = NULL;
        QActionGroup* groupby = NULL;
        if (!m_model->getWhereClause().contains("ORDER BY")) {
            groupbymenu = m_headerMenu->addMenu(i18nc("Noun, Menu name", "Group by"));
            groupby = new QActionGroup(groupbymenu);
            m_actGroupByNone = groupbymenu->addAction(i18nc("Noun, grouping option", "None"));
            if (m_actGroupByNone) {
                m_actGroupByNone->setIcon(KIcon("dialog-cancel"));
                m_actGroupByNone->setCheckable(true);
                m_actGroupByNone->setChecked(m_groupby == "");
                m_actGroupByNone->setData("");
                groupby->addAction(m_actGroupByNone);
            }
            if (m_proxyModel) {
                QAction* actSort = groupbymenu->addAction(i18nc("Noun, grouping option", "Sorted column"));
                if (actSort) {
                    actSort->setIcon(KIcon("view-sort-ascending"));
                    actSort->setCheckable(true);
                    actSort->setChecked(m_groupby == "#");
                    actSort->setData("#");
                    groupby->addAction(actSort);
                }
            }
            groupbymenu->addSeparator();
        }

        // Set right click menu
        if (m_model) {
            QList<SKGDocument::SKGModelTemplate> schemas = m_model->getSchemas();
            int nbSchemas = schemas.count();
            if (nbSchemas) {
                QMenu* viewAppearanceMenu = columns->addMenu(KIcon("view-file-columns"), i18nc("Noun, user action", "View appearance"));

                for (int i = 0; i < nbSchemas; ++i) {
                    SKGDocument::SKGModelTemplate schema = schemas.at(i);
                    QAction* act = viewAppearanceMenu->addAction(schema.name);
                    if (!schema.icon.isEmpty()) {
                        act->setIcon(KIcon(schema.icon));
                    }
                    act->setData(schema.schema);

                    connect(act, SIGNAL(triggered(bool)), this, SLOT(changeSchema()));
                }
            }
        }

        QAction* actResize = columns->addAction(KIcon("zoom-fit-width"), i18nc("Noun, user action", "Resize to content"));
        connect(actResize, SIGNAL(triggered(bool)), this, SLOT(resizeColumnsToContents()));

        m_actAutoResize = columns->addAction(i18nc("Noun, user action", "Auto resize"));
        m_actAutoResize->setCheckable(true);
        m_actAutoResize->setChecked(m_autoResize);
        connect(m_actAutoResize, SIGNAL(triggered(bool)), this, SLOT(switchAutoResize()));

        QAction* actAlternatingRowColors = m_headerMenu->addAction(i18nc("Noun, user action", "Alternate row colors"));
        if (actAlternatingRowColors) {
            actAlternatingRowColors->setCheckable(true);
            actAlternatingRowColors->setChecked(alternatingRowColors());
            connect(actAlternatingRowColors, SIGNAL(triggered(bool)), this, SLOT(setAlternatingRowColors(bool)));
        }

        if (m_document) {
            QAction* actDefault = m_headerMenu->addAction(KIcon("document-save"), i18nc("Noun, user action", "Save parameters"));
            connect(actDefault, SIGNAL(triggered(bool)), this, SLOT(saveDefaultClicked()));
        }

        columns->addSeparator();

        if (m_model) {
            // Build menus for columns
            QHeaderView* hHeader = header();
            int nbcol = hHeader->count();
            for (int i = 0; i < nbcol; ++i) {
                int idx = hHeader->logicalIndex(i);
                QString col = m_model->headerData(idx, Qt::Horizontal, Qt::UserRole).toString();
                QStringList values = col.split('|');

                if (!m_autoResizeDone) {
                    if (values.count() > 1) {
                        hHeader->setSectionHidden(idx, values.at(1) == "N");
                    }
                    if (values.count() > 2) {
                        int s = SKGServices::stringToInt(values.at(2));
                        if (s > 0) {
                            hHeader->resizeSection(idx, s);
                        }
                    }
                }

                // Column menu
                QAction* act = columns->addAction(values.at(0));
                if (act) {
                    act->setCheckable(true);
                    act->setChecked(!hHeader->isSectionHidden(idx));
                    act->setIcon(m_model->headerData(idx, Qt::Horizontal, Qt::DecorationRole).value<QIcon>());
                    act->setData(idx);
                    act->setEnabled(i > 0);

                    connect(act, SIGNAL(triggered(bool)), this, SLOT(showHideColumn()));

                    // Group by menu
                    QString att = m_model->getAttribute(idx);
                    if (groupbymenu && groupby) {
                        QAction* act2 = groupbymenu->addAction(values.at(0));
                        if (act2) {
                            act2->setCheckable(true);
                            act2->setChecked(att == m_groupby);
                            act2->setIcon(act->icon());
                            act2->setData(att);
                            groupby->addAction(act2);
                        }
                    }
                }
            }
            m_autoResizeDone = true;
        }
        if (groupby) {
            connect(groupby, SIGNAL(triggered(QAction*)), this, SLOT(groupByChanged(QAction*)));
        }
        m_headerMenu->addSeparator();

        QAction* actExport = m_headerMenu->addAction(KIcon("document-export"), i18nc("Noun, user action", "Export..."));
        connect(actExport, SIGNAL(triggered(bool)), this, SLOT(onExport()));

        if (m_autoResize) {
            resizeColumnsToContentsDelayed();
        }
    }
}

void SKGTreeView::setSelectionModel(QItemSelectionModel* iSelectionModel)
{
    if (this->selectionModel()) {
        disconnect(this->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(onSelectionChanged()));
    }
    QTreeView::setSelectionModel(iSelectionModel);
    if (iSelectionModel) {
        connect(iSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(onSelectionChanged()));
    }
}

void SKGTreeView::onSelectionChanged()
{
    SKGObjectBase::SKGListSKGObjectBase selection;
    QItemSelectionModel* selModel = selectionModel();
    if (selModel) {
        if (m_model) {
            QModelIndexList indexes = selModel->selectedRows();
            foreach(const QModelIndex & index, indexes) {
                QModelIndex idxs = (m_proxyModel ? m_proxyModel->mapToSource(index) : index);
                SKGObjectBase obj = m_model->getObject(idxs);
                selection.push_back(obj);
            }
        }
    }

    if (selection != m_lastSelection) {
        m_lastSelection = selection;
        m_timerSelectionChanged.start(300);
    }
}

void SKGTreeView::saveDefaultClicked()
{
    if (m_document) {
        SKGError err;
        SKGBEGINTRANSACTION(*m_document, i18nc("Noun, name of the user action", "Save default parameters"), err);
        err = m_document->setParameter(m_parameterName, getState());
    }
}

void SKGTreeView::switchAutoResize()
{
    m_autoResize = m_actAutoResize->isChecked();
    header()->setResizeMode(m_autoResize ? QHeaderView::Fixed : QHeaderView::Interactive);
    if (m_autoResize) {
        resizeColumnsToContentsDelayed();
    } else {
        m_timerDelayedResize.stop();
        m_autoResizeDone = false;
    }
}

void SKGTreeView::groupByChanged(QAction* iAction)
{
    if (m_model && m_model->isRefreshBlocked()) {
        return;
    }

    if (iAction && m_model) {
        m_groupby = iAction->data().toString();;
        QString att = m_groupby;
        if (att == "#" && m_proxyModel) {
            att = m_model->getAttribute(m_proxyModel->sortColumn());
        }
        m_model->setGroupBy(att);
        m_model->refresh();

        refreshExpandCollapse();
    }
}

void SKGTreeView::onSortChanged(int iIndex, Qt::SortOrder iOrder)
{
    Q_UNUSED(iOrder);
    if (m_groupby == "#" && m_model) {
        m_model->setGroupBy(m_model->getAttribute(iIndex));
        m_model->refresh();
    }

    m_timerScrollSelection.start(300);
}

void SKGTreeView::showHideColumn()
{
    QAction* send = static_cast<QAction*>(this->sender());
    if (send && m_model) {
        QHeaderView* hHeader = header();

        int idx = send->data().toInt();
        bool hidden = !hHeader->isSectionHidden(idx);
        hHeader->setSectionHidden(idx, hidden);

        m_model->setSupportedAttributes(getCurrentSchema());
        if (!hidden) {
            resizeColumnToContents(idx);
        }

        m_model->dataModified();
    }
}

void SKGTreeView::resetColumnsOrder()
{
    QHeaderView* hHeader = header();
    int nbcol = hHeader->count();
    for (int i = 0; i < nbcol; ++i) {
        int idx = hHeader->visualIndex(i);
        if (idx != i) {
            hHeader->moveSection(idx, i);
        }
    }
}

void SKGTreeView::changeSchema()
{
    QStringList list;

    QAction* send = static_cast<QAction*>(this->sender());
    if (send) {
        list = SKGServices::splitCSVLine(send->data().toString(), ';');
    }

    if (m_model) {
        // Reset column oder
        resetColumnsOrder();

        m_model->setSupportedAttributes(list);
        bool tmp = m_autoResizeDone;
        m_autoResizeDone = false;
        m_model->dataModified();
        m_autoResizeDone = tmp;
        header()->setSortIndicator(0, Qt::AscendingOrder);
    }
}

QStringList SKGTreeView::getCurrentSchema() const
{
    QStringList list;
    QHeaderView* hHeader = header();
    if (hHeader && m_model) {
        int nb = hHeader->count();
        if (nb) {
            QString att;
            for (int i = 0; i < nb; ++i) {
                int idx = hHeader->logicalIndex(i);
                att = m_model->getAttribute(idx);
                att += QString("|") % (hHeader->isSectionHidden(idx) ? "N" : "Y");
                att += QString("|") % SKGServices::intToString(hHeader->sectionSize(idx));

                list.push_back(att);
            }
        }
    }
    return list;
}
void SKGTreeView::setAlternatingRowColors(bool enable)
{
    QTreeView::setAlternatingRowColors(enable);
}

SKGObjectBase SKGTreeView::getFirstSelectedObject()
{
    return m_lastSelection.value(0);
}

SKGObjectBase::SKGListSKGObjectBase SKGTreeView::getSelectedObjects()
{
    return m_lastSelection;
}

int SKGTreeView::getNbSelectedObjects()
{
    return m_lastSelection.count();
}

void SKGTreeView::saveSelection()
{
    SKGTRACEINFUNC(10);

    m_selection.clear();

    SKGObjectBase::SKGListSKGObjectBase objs = getSelectedObjects();
    int nb = objs.count();
    // We save the selection only if not too big
    if (nb <= 100) {
        for (int i = 0; i < nb; ++i) {
            QString id = objs.at(i).getUniqueID();
            m_selection.push_back(id);
        }
    }
    SKGTRACEL(10) << m_selection.count() << " objects saved" << endl;
}

void SKGTreeView::selectObject(const QString& iUniqueID)
{
    SKGTRACEINFUNC(10);
    QStringList tmp;
    tmp.push_back(iUniqueID);
    selectObjects(tmp, true);
}

void SKGTreeView::selectObjects(const QStringList& iUniqueIDs, bool iFocusOnFirstOne)
{
    SKGTRACEINFUNC(10);
    SKGTRACEL(10) << iUniqueIDs.count() << " objects to select" << endl;
    int nbset = 0;
    QItemSelectionModel* selModel = selectionModel();
    if (selModel) {
        bool previous = selModel->blockSignals(true);

        selModel->clearSelection();

        if (m_model) {
            // Get all indexes
            QList<QModelIndex> items;
            items.push_back(QModelIndex());
            for (int i = 0; i < items.count(); ++i) {  // Dynamic size because the list is modified
                QModelIndex mi = items.at(i);
                int nbRows = m_model->rowCount(mi);
                for (int j = 0; j < nbRows; ++j) {
                    items.push_back(m_model->index(j, 0, mi));
                }
            }
            items.removeAt(0);

            int nbRows = items.count();
            if (nbRows) {
                // Expand nodes
                bool previousForThis = this->blockSignals(true);
                foreach(const QString & exp, m_expandedNodes) {
                    for (int i = 0; i < nbRows; ++i) {
                        QModelIndex index = items.at(i);
                        SKGObjectBase obj = m_model->getObject(index);
                        if (obj.getUniqueID() == exp) {
                            QModelIndex idxs = (m_proxyModel ? m_proxyModel->mapFromSource(index) : index);
                            setExpanded(idxs, true);
                            break;
                        }
                    }
                }
                this->blockSignals(previousForThis);

                // Set selection
                bool focusDone = false;
                foreach(const QString & sel, iUniqueIDs) {
                    for (int i = 0; i < nbRows; ++i) {
                        QModelIndex index = items.at(i);
                        SKGObjectBase obj = m_model->getObject(index);
                        if (obj.getUniqueID() == sel) {
                            QModelIndex idxs = (m_proxyModel ? m_proxyModel->mapFromSource(index) : index);
                            selModel->select(idxs, QItemSelectionModel::Select | QItemSelectionModel::Rows);
                            ++nbset;
                            if (iFocusOnFirstOne && !focusDone) {
                                scrollTo(idxs);
                                focusDone = true;
                            }
                            break;
                        }
                    }
                }
            }
        }
        selModel->blockSignals(previous);
    }

    SKGTRACEL(10) << nbset << " objects selected" << endl;

    onSelectionChanged();
}

void SKGTreeView::resetSelection()
{
    SKGTRACEINFUNC(10);

    selectObjects(m_selection);
}

void SKGTreeView::scroolOnSelection()
{
    SKGObjectBase::SKGListSKGObjectBase selection;
    QItemSelectionModel* selModel = selectionModel();
    if (selModel) {
        if (m_model) {
            QModelIndexList indexes = selModel->selectedRows();
            if (indexes.count()) {
                scrollTo(indexes.at(0));
            }
        }
    }
}

void SKGTreeView::onExpand(const QModelIndex& index)
{
    SKGTRACEINFUNC(10);
    if (index.isValid() && m_model) {
        QModelIndex idxs = (m_proxyModel ? m_proxyModel->mapToSource(index) : index);

        SKGObjectBase obj = m_model->getObject(idxs);
        QString id = obj.getUniqueID();
        m_expandedNodes.push_back(id);
    }

    if (m_autoResize) {
        resizeColumnsToContentsDelayed();
    }
}

void SKGTreeView::expandAll()
{
    SKGTRACEINFUNC(10);
    QTreeView::expandAll();

    if (m_autoResize) {
        resizeColumnsToContentsDelayed();
    }
}

void SKGTreeView::onCollapse(const QModelIndex& index)
{
    SKGTRACEINFUNC(10);
    if (index.isValid() && m_model) {
        QModelIndex idxs = (m_proxyModel ? m_proxyModel->mapToSource(index) : index);

        SKGObjectBase obj = m_model->getObject(idxs);

        QString id = obj.getUniqueID();
        m_expandedNodes.removeOne(id);
    }

    if (m_autoResize) {
        resizeColumnsToContentsDelayed();
    }
}

void SKGTreeView::onClick(const QModelIndex& index)
{
    SKGTRACEINFUNC(10);
    if (index.isValid() && m_actExpandAll && m_actExpandAll->isVisible()) {
        this->setExpanded(index, !this->isExpanded(index));
    }
}

void SKGTreeView::copy()
{
    QItemSelectionModel* selection = selectionModel();
    if (selection) {
        QModelIndexList indexes = selection->selectedIndexes();

        if (indexes.size() < 1) {
            return;
        }

        qSort(indexes.begin(), indexes.end());

        // You need a pair of indexes to find the row changes
        QModelIndex previous = indexes.first();
        indexes.removeFirst();
        QString header_text;
        bool header_done = false;
        QString selected_text;
        foreach(const QModelIndex & current, indexes) {
            selected_text.append(model()->data(previous).toString());
            if (!header_done) {
                header_text.append(model()->headerData(previous.column(), Qt::Horizontal).toString());
            }
            if (current.row() != previous.row()) {
                selected_text.append(QLatin1Char('\n'));
                header_done = true;
            } else {
                selected_text.append(QLatin1Char(';'));
                if (!header_done) {
                    header_text.append(QLatin1Char(';'));
                }
            }
            previous = current;
        }

        // add last element
        selected_text.append(model()->data(previous).toString());
        selected_text.append(QLatin1Char('\n'));
        QApplication::clipboard()->setText(header_text + '\n' + selected_text);
    }
}

void SKGTreeView::setZoomPosition(int iZoomPosition)
{
    int newZoomPos = qMax(qMin(iZoomPosition, 10), -10);
    if (newZoomPos != zoomPosition() && m_fontOriginalPointSize + newZoomPos > 1) {
        QFont newFont = this->font();
        newFont.setPointSize(m_fontOriginalPointSize + newZoomPos);
        int newIconSize = qMax(m_iconOriginalSize + newZoomPos, 1);

        this->setFont(newFont);
        this->setIconSize(QSize(newIconSize, newIconSize));
        header()->setIconSize(QSize(newIconSize, newIconSize));

        if (m_autoResize) {
            resizeColumnsToContentsDelayed();
        }

        Q_EMIT zoomChanged(newZoomPos);
    }
}

int SKGTreeView::zoomPosition()
{
    return this->font().pointSize() - m_fontOriginalPointSize;
}

bool SKGTreeView::eventFilter(QObject* object, QEvent* iEvent)
{
    if (object == this && iEvent != NULL && iEvent->type() == QEvent::Wheel) {
        QWheelEvent* e = static_cast<QWheelEvent*>(iEvent);
        if (m_textResizable && e && e->orientation() == Qt::Vertical && QApplication::keyboardModifiers() &Qt::ControlModifier) {
            int numDegrees = e->delta() / 8;
            int numTicks = numDegrees / 15;

            setZoomPosition(zoomPosition() + (numTicks > 0 ? 1 : -1));
            e->setAccepted(true);
            return true;
        }
    }
    if (object == this && iEvent != NULL && iEvent->type() == QEvent::KeyPress) {
        QKeyEvent* kevent = static_cast<QKeyEvent*>(iEvent);
        if (kevent) {
            if (kevent->matches(QKeySequence::Copy) && this->state() != QAbstractItemView::EditingState) {
                copy();
                if (iEvent) {
                    iEvent->accept();
                }
                return true;  // stop the process
            }
        }
    }
    return QTreeView::eventFilter(object, iEvent);
}

void SKGTreeView::mousePressEvent(QMouseEvent* iEvent)
{
    if (iEvent && iEvent->button() == Qt::LeftButton && !(this->indexAt(iEvent->pos()).isValid())) {
        Q_EMIT clickEmptyArea();
        clearSelection();
    }
    QTreeView::mousePressEvent(iEvent);
}

bool SKGTreeView::isTextResizable() const
{
    return m_textResizable;
}

void SKGTreeView::setTextResizable(bool resizable)
{
    m_textResizable = resizable;
}

KTextBrowser* SKGTreeView::getTextBrowser() const
{
    KTextBrowser* output = new KTextBrowser();
    QTextCursor tcursor = output->textCursor();
    tcursor.beginEditBlock();

    // Create table format
    QTextTableFormat tableFormat;
    tableFormat.setAlignment(Qt::AlignHCenter);
    tableFormat.setAlignment(Qt::AlignLeft);
    tableFormat.setBackground(QColor("#ffffff"));
    tableFormat.setCellPadding(5);
    tableFormat.setCellSpacing(5);

    // Create table
    SKGStringListList table = getTable();
    int nbRows = table.count();
    int nbCol = table.at(0).count();

    QTextTable* tableau = tcursor.insertTable(nbRows, nbCol, tableFormat);

    // Create frame
    QTextFrame* frame = tcursor.currentFrame();
    QTextFrameFormat frameFormat = frame->frameFormat();
    frameFormat.setBorder(0);
    frame->setFrameFormat(frameFormat);

    // Create header table format
    QTextCharFormat headerFormat;
    headerFormat.setFontPointSize(6);
    headerFormat.setFontWeight(QFont::Bold);

    // Create text format
    QTextCharFormat textFormat;
    textFormat.setFontPointSize(6);

    // Create header
    for (int r = 0; r < nbRows; ++r) {
        QStringList line = table.at(r);
        for (int c = 0 ; c < nbCol ; ++c) {
            QTextCursor cellCursor = tableau->cellAt(r, c).firstCursorPosition();
            cellCursor.insertText(line.at(c) , (r == 0 ? headerFormat : textFormat));
        }
    }

    // End
    tcursor.endEditBlock();

    return output;
}

SKGStringListList SKGTreeView::getTable(const QModelIndex& iIndex) const
{
    // Build table
    SKGStringListList table;

    // Get header names
    if (m_model) {
        // Header
        int nb = m_model->columnCount();
        if (!iIndex.isValid()) {
            QStringList cols;
            for (int i = 0; i < nb; ++i) {
                cols.append(m_model->headerData(i, Qt::Horizontal, Qt::UserRole).toString().split('|').at(0));
            }
            table.append(cols);
        }

        // Get content
        int nb2 = m_model->rowCount(iIndex);
        for (int i = 0; i < nb2; ++i) {
            QStringList row;
            for (int j = 0; j < nb; j++) {
                // We have to check the type for 214849
                QModelIndex idx = m_model->index(i, j, iIndex);

                SKGServices::AttributeType type = m_model->getAttributeType(j);
                QString display = m_model->data(idx, type == SKGServices::FLOAT || m_model->getObject(idx).getTable().isEmpty() ?  Qt::DisplayRole : Qt::UserRole).toString();
                if (display.isEmpty()) {
                    display = m_model->data(idx, Qt::DisplayRole).toString();
                }
                row.append(display);
            }

            table.append(row);

            QModelIndex idx0 = m_model->index(i, 0, iIndex);
            if (m_model->hasChildren(idx0)) {
                table.append(getTable(idx0));
            }
        }
    }
    return table;
}

SKGError SKGTreeView::exportInFile(const QString& iFileName)
{
    SKGError err;
    _SKGTRACEINFUNC(10);
    QString codec = QTextCodec::codecForLocale()->name();
    QString extension = QFileInfo(iFileName).suffix().toUpper();
    if (extension == "CSV") {
        // Write file
        KSaveFile file(iFileName);
        if (!file.open()) {
            err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed" , iFileName));
        } else {
            QTextStream out(&file);
            out.setCodec(codec.toAscii().constData());
            QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_CSV);
            int nbl = dump.count();
            for (int i = 0; i < nbl; ++i) {
                out << dump[i] << endl;
            }
        }

        // Close file
        file.finalize();
        file.close();
    } else if (extension == "PDF") {
        QImage image(this->size(), QImage::Format_ARGB32);
        QPainter painter(&image);
        this->render(&painter);
        painter.end();

        {
            QPrinter printer(QPrinter::HighResolution);
            printer.setOutputFileName(iFileName);
            QPainter newPainter(&printer);

            QRect painterRect = newPainter.viewport();
            QSize imageSize = image.size();
            imageSize.scale(painterRect.size(), Qt::KeepAspectRatio);
            newPainter.setViewport(painterRect.x(), painterRect.y(), imageSize.width(), imageSize.height());
            newPainter.setWindow(image.rect());
            newPainter.drawImage(0, 0, image);
            newPainter.end();
        }
    } else if (extension == "SVG") {
        QSvgGenerator generator;
        generator.setFileName(iFileName);
        generator.setTitle(i18nc("Title of the content SVG export", "Skrooge SVG export"));
        generator.setDescription(i18nc("Description of the content SVG export", "A SVG drawing created by the Skrooge."));

        QPainter painter(&generator);
        QWidget* w = this->viewport();
        w->render(&painter);
        generator.setSize(QSize(w->widthMM(), w->heightMM()));
        generator.setViewBox(QRect(0, 0, w->widthMM(), w->heightMM()));

        painter.end();
    } else if (extension == "HTML") {
        // Write file
        KSaveFile file(iFileName);
        if (!file.open()) {
            err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed" , iFileName));
        } else {
            QTextStream out(&file);
            out.setCodec(codec.toAscii().constData());
            KTextBrowser* tb = getTextBrowser();
            if (tb) {
                out << tb->toHtml().replace("<meta name=\"qrichtext\" content=\"1\" />", "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />") << endl;

                delete tb;
            }
        }

        // Close file
        file.finalize();
        file.close();
    } else if (extension == "ODT") {
        KTextBrowser* tb = getTextBrowser();
        if (tb) {
            QTextDocument doc;
            doc.setHtml(tb->toHtml());

            QTextDocumentWriter docWriter(iFileName);
            docWriter.write(&doc);

            delete tb;
        }
    } else {
        // Write file
        KSaveFile file(iFileName);
        if (!file.open()) {
            err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed" , iFileName));
        } else {
            QTextStream out(&file);
            out.setCodec(codec.toAscii().constData());
            QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_TEXT);
            int nbl = dump.count();
            for (int i = 0; i < nbl; ++i) {
                out << dump[i] << endl;
            }
        }

        // Close file
        file.finalize();
        file.close();
    }


    return err;
}

void SKGTreeView::onExport()
{
    _SKGTRACEINFUNC(10);
    QString fileName = SKGMainPanel::getSaveFileName("kfiledialog:///IMPEXP", "text/csv text/plain text/html application/vnd.oasis.opendocument.text  image/svg+xml application/pdf" , this);
    if (!fileName.isEmpty()) {
        SKGError err = exportInFile(fileName);
        SKGMainPanel::displayErrorMessage(err);
        QDesktopServices::openUrl(QUrl(fileName));
    }
}

void SKGTreeView::setModel(QAbstractItemModel* iModel)
{
    if (iModel != this->model()) {
        m_model = static_cast<SKGObjectModelBase*>(iModel);
        m_proxyModel = qobject_cast<SKGSortFilterProxyModel*> (iModel);
        if (m_proxyModel) {
            m_model = static_cast<SKGObjectModelBase*>(m_proxyModel->sourceModel());
        }

        if (m_model) {
            connect(m_model, SIGNAL(afterReset()), this, SLOT(setupHeaderMenu()));
            connect(m_model, SIGNAL(afterReset()), this, SLOT(onSelectionChanged()));
        }
        QTreeView::setModel(iModel);

        refreshExpandCollapse();
    }
}

KMenu* SKGTreeView::getHeaderMenu() const
{
    return m_headerMenu;
}

#include "skgtreeview.moc"
