/***************************************************************************
 *   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 <klocalizedstring.h>
#include <kstandardaction.h>

#include <qapplication.h>
#include <qbasictimer.h>
#include <qclipboard.h>
#include <qdesktopservices.h>
#include <qdom.h>
#include <qevent.h>
#include <qfileinfo.h>
#include <qheaderview.h>
#include <qicon.h>
#include <qmenu.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qpushbutton.h>
#include <qsavefile.h>
#include <qscrollbar.h>
#include <qsvggenerator.h>
#include <qtextbrowser.h>
#include <qtextcodec.h>
#include <qtextdocumentwriter.h>
#include <qtextobject.h>
#include <qtexttable.h>
#include <qtimer.h>

#include <algorithm>

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

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

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

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

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

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

    setContextMenuPolicy(Qt::ActionsContextMenu);
    connect(hori, &QHeaderView::customContextMenuRequested, this, static_cast<void (SKGTreeView::*)(const QPoint)>(&SKGTreeView::showHeaderMenu));
    connect(hori, &QHeaderView::sortIndicatorChanged, this, &SKGTreeView::onSortChanged);

    //
    QAction* actCopy = KStandardAction::copy(this, SLOT(copy()), nullptr);
    actCopy->setProperty("isShortcutConfigurable", false);
    actCopy->setShortcutContext(Qt::WidgetShortcut);

    m_actExpandAll = new QAction(SKGServices::fromTheme(QStringLiteral("format-indent-more")), i18nc("Noun, user action", "Expand all"), this);
    m_actExpandAll->setShortcut(Qt::ALT + Qt::Key_Plus);
    m_actExpandAll->setProperty("isShortcutConfigurable", false);
    m_actExpandAll->setShortcutContext(Qt::WidgetShortcut);
    connect(m_actExpandAll, &QAction::triggered, this, &SKGTreeView::expandAll);

    m_actCollapseAll = new QAction(SKGServices::fromTheme(QStringLiteral("format-indent-less")), i18nc("Noun, user action", "Collapse all"), this);
    m_actCollapseAll->setShortcut(Qt::ALT + Qt::Key_Minus);
    m_actCollapseAll->setProperty("isShortcutConfigurable", false);
    m_actCollapseAll->setShortcutContext(Qt::WidgetShortcut);
    connect(m_actCollapseAll, &QAction::triggered, this, &SKGTreeView::collapseAll);
    if (SKGMainPanel::getMainPanel()) {
        SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_copy"), actCopy);
        SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_expandall"), m_actExpandAll);
        SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_collapseal"), m_actCollapseAll);
    }

    // Scroll bars
    connect(horizontalScrollBar(), &QScrollBar::actionTriggered, this, &SKGTreeView::onActionTriggered);
    connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, &SKGTreeView::onActionTriggered);
    connect(horizontalScrollBar(), &QScrollBar::rangeChanged, this, &SKGTreeView::onRangeChanged);
    connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &SKGTreeView::onRangeChanged);

    // Headers
    hori->setSectionsMovable(true);
    hori->setSectionResizeMode(QHeaderView::Fixed);
    setWordWrap(false);

    connect(header(), &QHeaderView::sectionMoved, this, &SKGTreeView::setupHeaderMenu, Qt::QueuedConnection);

    connect(this, &SKGTreeView::clicked, this, &SKGTreeView::onClick);
    connect(this, &SKGTreeView::collapsed, this, &SKGTreeView::onCollapse);
    connect(this, &SKGTreeView::expanded, this, &SKGTreeView::onExpand);


    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 = nullptr;
    m_headerMenu = nullptr;
    m_proxyModel = nullptr;
    m_model = nullptr;
    m_actExpandAll = nullptr;
    m_actCollapseAll = nullptr;
}

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

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

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

            if (m_proxyModel) {
                root.setAttribute(QStringLiteral("sortPreviousColumn"), SKGServices::intToString(m_proxyModel->getPreviousSortColumn()));
            }
        }
        root.setAttribute(QStringLiteral("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) ? QStringLiteral("N") : QStringLiteral("Y"));
            }
            root.setAttribute(QStringLiteral("columns"), columns);
            if (!m_autoResize) {
                root.setAttribute(QStringLiteral("columnsSize"), columnsSize);
            }
            root.setAttribute(QStringLiteral("columnsVisibility"), columnsVisibility);
            root.setAttribute(QStringLiteral("columnsAutoResize"), m_autoResize ? QStringLiteral("Y") : QStringLiteral("N"));
        }

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

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

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

    QDomDocument doc(QStringLiteral("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(QStringLiteral("sortOrder"));
        QString sortColumn = root.attribute(QStringLiteral("sortColumn"));
        QString sortPreviousColumn = root.attribute(QStringLiteral("sortPreviousColumn"));
        m_groupby = root.attribute(QStringLiteral("groupBy"));
        QString columns = root.attribute(QStringLiteral("columns"));
        QString columnsSize = root.attribute(QStringLiteral("columnsSize"));
        QString columnsVisibility = root.attribute(QStringLiteral("columnsVisibility"));
        QString columnsAutoResize = root.attribute(QStringLiteral("columnsAutoResize"));
        QString alternatingRowColors2 = root.attribute(QStringLiteral("alternatingRowColors"));
        QString zoomPositionString = root.attribute(QStringLiteral("zoomPosition"));
        QString expandedGroups = root.attribute(QStringLiteral("expandedGroups"));
        stickH = (root.attribute(QStringLiteral("stickH")) == QStringLiteral("Y"));
        stickV = (root.attribute(QStringLiteral("stickV")) == QStringLiteral("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.at(i) % '|' % visibilities.at(i);
                    if (nbsizes == nb) {
                        listAtt[i] = listAtt.at(i) % '|' % sizes.at(i);
                    }
                }
            }
        }
        if (m_model) {
            m_model->setSupportedAttributes(listAtt);
        }

        // Set autoResize
        if (!columnsAutoResize.isEmpty()) {
            m_autoResize = (columnsAutoResize == QStringLiteral("Y"));
            header()->setSectionResizeMode(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 == QStringLiteral("#")) {
                att = sortColumn;
            }
            m_model->setGroupBy(att);
            m_model->dataModified();

            refreshExpandCollapse();
        }

        // Set alternatingRowColors
        if (!alternatingRowColors2.isEmpty()) {
            setAlternatingRowColors(alternatingRowColors2 == QStringLiteral("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 = QStringLiteral("");
            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::respanFirstColumns()
{
    // Span groups
    int nbRow = m_model->rowCount();
    for (int row = 0; row < nbRow; ++row) {
        this -> setFirstColumnSpanned(row, QModelIndex(), !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()) {
        auto 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);
    respanFirstColumns();

    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(nullptr);
    if (m_model && m_headerMenu) {
        // Corner button on tables to show the contextual menu
        auto btn = new QPushButton(this);
        btn->setIcon(SKGServices::fromTheme(QStringLiteral("configure")));
        btn->setFocusPolicy(Qt::NoFocus);
        connect(btn, &QPushButton::clicked, this, static_cast<void (SKGTreeView::*)()>(&SKGTreeView::showHeaderMenu));
        setCornerWidget(btn);

        m_headerMenu->clear();

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

        // Get current groupby column
        QMenu* groupbymenu = nullptr;
        QActionGroup* groupby = nullptr;
        if (!m_model->getWhereClause().contains(QStringLiteral("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(SKGServices::fromTheme(QStringLiteral("dialog-cancel")));
                m_actGroupByNone->setCheckable(true);
                m_actGroupByNone->setChecked(m_groupby.isEmpty());
                m_actGroupByNone->setData(QStringLiteral(""));
                groupby->addAction(m_actGroupByNone);
            }
            if (m_proxyModel) {
                QAction* actSort = groupbymenu->addAction(i18nc("Noun, grouping option", "Sorted column"));
                if (actSort) {
                    actSort->setIcon(SKGServices::fromTheme(QStringLiteral("view-sort-ascending")));
                    actSort->setCheckable(true);
                    actSort->setChecked(m_groupby == QStringLiteral("#"));
                    actSort->setData(QStringLiteral("#"));
                    groupby->addAction(actSort);
                }
            }
            groupbymenu->addSeparator();
        }

        // Set right click menu
        SKGDocument::SKGModelTemplateList schemas = m_model->getSchemas();
        int nbSchemas = schemas.count();
        if (nbSchemas) {
            QMenu* viewAppearanceMenu = columns->addMenu(SKGServices::fromTheme(QStringLiteral("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(SKGServices::fromTheme(schema.icon));
                }
                act->setData(schema.schema);

                connect(act, &QAction::triggered, this, &SKGTreeView::changeSchema);
            }
        }

        QAction* actResize = columns->addAction(SKGServices::fromTheme(QStringLiteral("zoom-fit-width")), i18nc("Noun, user action", "Resize to content"));
        connect(actResize, &QAction::triggered, this, &SKGTreeView::resizeColumnsToContents);

        m_actAutoResize = columns->addAction(SKGServices::fromTheme(QStringLiteral("zoom-fit-width"), QStringList() << QStringLiteral("run-build")), i18nc("Noun, user action", "Auto resize"));
        m_actAutoResize->setCheckable(true);
        m_actAutoResize->setChecked(m_autoResize);
        connect(m_actAutoResize, &QAction::triggered, this, &SKGTreeView::switchAutoResize);

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

        if (m_document) {
            QAction* actDefault = m_headerMenu->addAction(SKGServices::fromTheme(QStringLiteral("document-save")), i18nc("Noun, user action", "Save parameters"));
            connect(actDefault, &QAction::triggered, this, &SKGTreeView::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) == QStringLiteral("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, &QAction::triggered, this, &SKGTreeView::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, &QActionGroup::triggered, this, &SKGTreeView::groupByChanged);
        }
        m_headerMenu->addSeparator();

        QAction* actExport = m_headerMenu->addAction(SKGServices::fromTheme(QStringLiteral("document-export")), i18nc("Noun, user action", "Export..."));
        connect(actExport, &QAction::triggered, this, &SKGTreeView::onExport);

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

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

void SKGTreeView::onSelectionChanged()
{
    SKGObjectBase::SKGListSKGObjectBase selection;
    QItemSelectionModel* selModel = selectionModel();
    if (selModel) {
        if (m_model) {
            auto indexes = selModel->selectedRows();
            selection.reserve(indexes.count());
            for (const auto& index : qAsConst(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()->setSectionResizeMode(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 == QStringLiteral("#") && 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 == QStringLiteral("#") && 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) {
        QHeaderView* hHeader = header();

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

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 += QStringLiteral("|") % (hHeader->isSectionHidden(idx) ? QStringLiteral("N") : QStringLiteral("Y"));
                att += QStringLiteral("|") % 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
            QVector<QModelIndex> items;
            items.reserve(items.count() * 2);
            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);
                for (const auto& exp : qAsConst(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;
                for (const auto& sel : qAsConst(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);
                            selModel->setCurrentIndex(idxs, QItemSelectionModel::NoUpdate);
                            ++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.isEmpty()) {
                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;
        }

        std::sort(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;
        for (const auto& current : qAsConst(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'));
        auto clipBoard = QApplication::clipboard();
        if (clipBoard) {
            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 != nullptr && 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 != nullptr && 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();
    }

    if (iEvent && iEvent->button() == Qt::LeftButton && m_proxyModel && m_model) {
        int propertyUUID = m_proxyModel->data(indexAt(iEvent->pos()), 101).toInt();
        if (propertyUUID) {
            SKGPropertyObject prop(m_model->getDocument(), propertyUUID);
            QDesktopServices::openUrl(prop.getUrl(true));
        }
    }

    QTreeView::mousePressEvent(iEvent);
}

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

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

QTextBrowser* SKGTreeView::getTextBrowser() const
{
    auto output = new QTextBrowser();
    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();
        int nb2 = m_model->rowCount(iIndex);
        table.reserve(1 + nb2 * 2);
        if (!iIndex.isValid()) {
            QStringList cols;
            cols.reserve(nb);
            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
        for (int i = 0; i < nb2; ++i) {
            QStringList row;
            row.reserve(nb);
            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 == QStringLiteral("CSV")) {
        // Write file
        QSaveFile file(iFileName);
        if (!file.open(QIODevice::WriteOnly)) {
            err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed" , iFileName));
        } else {
            QTextStream out(&file);
            out.setCodec(codec.toLatin1().constData());
            QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_CSV);
            int nbl = dump.count();
            for (int i = 0; i < nbl; ++i) {
                out << dump.at(i) << endl;
            }

            // Close file
            file.commit();
        }
    } else if (extension == QStringLiteral("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 == QStringLiteral("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 == QStringLiteral("HTML")) {
        // Write file
        QSaveFile file(iFileName);
        if (!file.open(QIODevice::WriteOnly)) {
            err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed" , iFileName));
        } else {
            QTextStream out(&file);
            out.setCodec(codec.toLatin1().constData());
            QTextBrowser* tb = getTextBrowser();
            if (tb) {
                out << tb->toHtml().replace(QStringLiteral("<meta name=\"qrichtext\" content=\"1\" />"), QStringLiteral("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />")) << endl;

                delete tb;
            }

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

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

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

            // Close file
            file.commit();
        }
    }

    return err;
}

void SKGTreeView::onExport()
{
    _SKGTRACEINFUNC(10);
    QString fileName = SKGMainPanel::getSaveFileName(QStringLiteral("kfiledialog:///IMPEXP"), QStringLiteral("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::fromLocalFile(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, &SKGObjectModelBase::afterReset, this, &SKGTreeView::setupHeaderMenu);
            connect(m_model, &SKGObjectModelBase::afterReset, this, &SKGTreeView::onSelectionChanged);
        }
        QTreeView::setModel(iModel);

        rebuildContextualMenu();
        refreshExpandCollapse();
    }
}

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

void SKGTreeView::rebuildContextualMenu()
{
    // Remove all Actions
    const auto list = actions();
    for (auto act : list) {
        removeAction(act);
    }

    if (selectionMode() != NoSelection) {
        // Build contextual menu
        insertGlobalAction(QStringLiteral("edit_copy"));
        insertGlobalAction(QStringLiteral("edit_expandall"));
        insertGlobalAction(QStringLiteral("edit_collapseal"));

        if (m_model && SKGMainPanel::getMainPanel()) {
            const auto list = SKGMainPanel::getMainPanel()->getActionsForContextualMenu(m_model->getRealTable());
            for (const auto& act : list) {
                if (act == nullptr) {
                    insertGlobalAction();
                } else {
                    insertAction(0, act);
                }
            }
        }
    }
}
