Warning, file /office/skrooge/skgbasegui/skgtreeview.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /***************************************************************************
0002  * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
0003  * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  ***************************************************************************/
0006 /** @file
0007  * A tree view with more features.
0008  *
0009  * @author Stephane MANKOWSKI / Guillaume DE BURE
0010  */
0011 #include "skgtreeview.h"
0012 
0013 #include <klocalizedstring.h>
0014 #include <kstandardaction.h>
0015 
0016 #include <qapplication.h>
0017 #include <qbasictimer.h>
0018 #include <qclipboard.h>
0019 #include <qdesktopservices.h>
0020 #include <qdom.h>
0021 #include <qevent.h>
0022 #include <qfileinfo.h>
0023 #include <qheaderview.h>
0024 #include <qicon.h>
0025 #include <qmenu.h>
0026 #include <qpainter.h>
0027 #include <qprinter.h>
0028 #include <qpushbutton.h>
0029 #include <qsavefile.h>
0030 #include <qscrollbar.h>
0031 #include <qsvggenerator.h>
0032 #include <qtextbrowser.h>
0033 #include <qtextcodec.h>
0034 #include <qtextdocumentwriter.h>
0035 #include <qtextobject.h>
0036 #include <qtexttable.h>
0037 #include <qtimer.h>
0038 
0039 #include <algorithm>
0040 
0041 #include "skgdocument.h"
0042 #include "skgerror.h"
0043 #include "skgmainpanel.h"
0044 #include "skgobjectbase.h"
0045 #include "skgobjectmodelbase.h"
0046 #include "skgpropertyobject.h"
0047 #include "skgservices.h"
0048 #include "skgsortfilterproxymodel.h"
0049 #include "skgtableview.h"
0050 #include "skgtraces.h"
0051 #include "skgtransactionmng.h"
0052 
0053 SKGTreeView::SKGTreeView(QWidget* iParent)
0054     : QTreeView(iParent),
0055       m_autoResize(true), m_autoResizeDone(false), m_actAutoResize(nullptr),
0056       m_document(nullptr), m_textResizable(true),
0057       m_model(nullptr), m_proxyModel(nullptr),
0058       m_actGroupByNone(nullptr),
0059       stickH(false), stickV(false)
0060 {
0061     // Initialize
0062     setTextElideMode(Qt::ElideMiddle);
0063     setAutoExpandDelay(300);
0064     setAnimated(true);
0065 
0066     m_timerDelayedResize.setSingleShot(true);
0067     connect(&m_timerDelayedResize, &QTimer::timeout, this, &SKGTreeView::resizeColumnsToContents, Qt::QueuedConnection);
0068 
0069     m_timerSelectionChanged.setSingleShot(true);
0070     connect(&m_timerSelectionChanged, &QTimer::timeout, this, &SKGTreeView::selectionChangedDelayed, Qt::QueuedConnection);
0071 
0072     m_timerScrollSelection.setSingleShot(true);
0073     connect(&m_timerScrollSelection, &QTimer::timeout, this, &SKGTreeView::scroolOnSelection, Qt::QueuedConnection);
0074 
0075     // Menu
0076     QHeaderView* hori = header();
0077     hori->setContextMenuPolicy(Qt::CustomContextMenu);
0078     m_headerMenu = new QMenu(this);
0079 
0080     setContextMenuPolicy(Qt::ActionsContextMenu);
0081     connect(hori, &QHeaderView::customContextMenuRequested, this, static_cast<void (SKGTreeView::*)(const QPoint)>(&SKGTreeView::showHeaderMenu));
0082     connect(hori, &QHeaderView::sortIndicatorChanged, this, &SKGTreeView::onSortChanged);
0083 
0084     //
0085     m_actCopy = KStandardAction::copy(this, SLOT(copy()), nullptr);
0086     m_actCopy->setProperty("isShortcutConfigurable", false);
0087     m_actCopy->setShortcutContext(Qt::WidgetShortcut);
0088 
0089     m_actExpandAll = new QAction(SKGServices::fromTheme(QStringLiteral("format-indent-more")), i18nc("Noun, user action", "Expand all"), this);
0090     m_actExpandAll->setShortcut(Qt::ALT + Qt::Key_Plus);
0091     m_actExpandAll->setProperty("isShortcutConfigurable", false);
0092     m_actExpandAll->setShortcutContext(Qt::WidgetShortcut);
0093     connect(m_actExpandAll, &QAction::triggered, this, &SKGTreeView::expandAll);
0094 
0095     m_actCollapseAll = new QAction(SKGServices::fromTheme(QStringLiteral("format-indent-less")), i18nc("Noun, user action", "Collapse all"), this);
0096     m_actCollapseAll->setShortcut(Qt::ALT + Qt::Key_Minus);
0097     m_actCollapseAll->setProperty("isShortcutConfigurable", false);
0098     m_actCollapseAll->setShortcutContext(Qt::WidgetShortcut);
0099     connect(m_actCollapseAll, &QAction::triggered, this, &SKGTreeView::collapseAll);
0100     if (SKGMainPanel::getMainPanel() != nullptr) {
0101         SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_copy"), m_actCopy);
0102         SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_expandall"), m_actExpandAll);
0103         SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_collapseall"), m_actCollapseAll);
0104     }
0105 
0106     // Scroll bars
0107     connect(horizontalScrollBar(), &QScrollBar::actionTriggered, this, &SKGTreeView::onActionTriggered);
0108     connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, &SKGTreeView::onActionTriggered);
0109     connect(horizontalScrollBar(), &QScrollBar::rangeChanged, this, &SKGTreeView::onRangeChanged);
0110     connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &SKGTreeView::onRangeChanged);
0111 
0112     // Headers
0113     hori->setSectionsMovable(true);
0114     hori->setSectionResizeMode(QHeaderView::Fixed);
0115     setWordWrap(false);
0116 
0117     connect(header(), &QHeaderView::sectionMoved, this, &SKGTreeView::setupHeaderMenu, Qt::QueuedConnection);
0118 
0119     connect(this, &SKGTreeView::clicked, this, &SKGTreeView::onClick);
0120     connect(this, &SKGTreeView::collapsed, this, &SKGTreeView::onCollapse);
0121     connect(this, &SKGTreeView::expanded, this, &SKGTreeView::onExpand);
0122 
0123     QWidget* vp = this->viewport();
0124     if (vp != nullptr) {
0125         vp->installEventFilter(this);
0126         this->installEventFilter(this);
0127     }
0128 
0129     // Save original size
0130     m_fontOriginalPointSize = this->font().pointSize();
0131     m_iconOriginalSize = this->iconSize().height();
0132     if (m_iconOriginalSize <= 0) {
0133         m_iconOriginalSize = 16;
0134     }
0135 }
0136 
0137 SKGTreeView::~SKGTreeView()
0138 {
0139     m_document = nullptr;
0140     m_headerMenu = nullptr;
0141     m_proxyModel = nullptr;
0142     m_model = nullptr;
0143     m_actExpandAll = nullptr;
0144     m_actCollapseAll = nullptr;
0145 }
0146 
0147 bool SKGTreeView::isAutoResized()
0148 {
0149     return m_autoResize;
0150 }
0151 
0152 QString SKGTreeView::getState()
0153 {
0154     SKGTRACEINFUNC(10)
0155     QDomDocument doc(QStringLiteral("SKGML"));
0156     QDomElement root = doc.createElement(QStringLiteral("parameters"));
0157     doc.appendChild(root);
0158 
0159     QHeaderView* hHeader = header();
0160     if ((hHeader != nullptr) && (m_model != nullptr)) {
0161         if (isSortingEnabled()) {
0162             root.setAttribute(QStringLiteral("sortOrder"), SKGServices::intToString(static_cast<int>(hHeader->sortIndicatorOrder())));
0163             root.setAttribute(QStringLiteral("sortColumn"), m_model->getAttribute(hHeader->sortIndicatorSection()));
0164 
0165             if (m_proxyModel != nullptr) {
0166                 root.setAttribute(QStringLiteral("sortPreviousColumn"), SKGServices::intToString(m_proxyModel->getPreviousSortColumn()));
0167             }
0168         }
0169         root.setAttribute(QStringLiteral("groupBy"), m_groupby);
0170 
0171         // Memorize order
0172         int nb = hHeader->count();
0173         if (nb != 0) {
0174             QString columns;
0175             QString columnsSize;
0176             QString columnsVisibility;
0177             for (int i = 0; i < nb; ++i) {
0178                 int idx = hHeader->logicalIndex(i);
0179                 if (i != 0) {
0180                     columns += ';';
0181                 }
0182                 columns += m_model->getAttribute(idx);
0183 
0184                 if (i != 0) {
0185                     columnsSize += ';';
0186                 }
0187                 columnsSize += SKGServices::intToString(hHeader->sectionSize(idx));
0188 
0189                 if (i != 0) {
0190                     columnsVisibility += ';';
0191                 }
0192                 columnsVisibility += (hHeader->isSectionHidden(idx) ? QStringLiteral("N") : QStringLiteral("Y"));
0193             }
0194             root.setAttribute(QStringLiteral("columns"), columns);
0195             if (!m_autoResize) {
0196                 root.setAttribute(QStringLiteral("columnsSize"), columnsSize);
0197             }
0198             root.setAttribute(QStringLiteral("columnsVisibility"), columnsVisibility);
0199             root.setAttribute(QStringLiteral("columnsAutoResize"), m_autoResize ? QStringLiteral("Y") : QStringLiteral("N"));
0200         }
0201 
0202         // Memorize expanded groups
0203         if (!m_groupby.isEmpty()) {
0204             root.setAttribute(QStringLiteral("expandedGroups"), m_expandedNodes.join(QStringLiteral(";")));
0205         }
0206     }
0207     root.setAttribute(QStringLiteral("alternatingRowColors"), alternatingRowColors() ? QStringLiteral("Y") : QStringLiteral("N"));
0208     root.setAttribute(QStringLiteral("zoomPosition"), SKGServices::intToString(zoomPosition()));
0209 
0210     QScrollBar* scroll2 = horizontalScrollBar();
0211     if ((scroll2 != nullptr) && scroll2->value() == scroll2->maximum() && scroll2->value() != scroll2->minimum()) {
0212         root.setAttribute(QStringLiteral("stickH"), QStringLiteral("Y"));
0213     }
0214     scroll2 = verticalScrollBar();
0215     if ((scroll2 != nullptr) && scroll2->value() == scroll2->maximum() && scroll2->value() != scroll2->minimum()) {
0216         root.setAttribute(QStringLiteral("stickV"), QStringLiteral("Y"));
0217     }
0218     return doc.toString(-1);
0219 }
0220 
0221 void SKGTreeView::setState(const QString& iState)
0222 {
0223     SKGTRACEINFUNC(10)
0224     resetColumnsOrder();
0225 
0226     QDomDocument doc(QStringLiteral("SKGML"));
0227 
0228     QString viewState = iState;
0229     if (viewState.isEmpty() && (m_document != nullptr)) {
0230         // Get default state
0231         viewState = m_document->getParameter(m_parameterName);
0232     }
0233 
0234     Qt::SortOrder qtsortorder = Qt::AscendingOrder;
0235     if (doc.setContent(viewState)) {
0236         QDomElement root = doc.documentElement();
0237 
0238         QString sortOrder = root.attribute(QStringLiteral("sortOrder"));
0239         QString sortColumn = root.attribute(QStringLiteral("sortColumn"));
0240         QString sortPreviousColumn = root.attribute(QStringLiteral("sortPreviousColumn"));
0241         m_groupby = root.attribute(QStringLiteral("groupBy"));
0242         QString columns = root.attribute(QStringLiteral("columns"));
0243         QString columnsSize = root.attribute(QStringLiteral("columnsSize"));
0244         QString columnsVisibility = root.attribute(QStringLiteral("columnsVisibility"));
0245         QString columnsAutoResize = root.attribute(QStringLiteral("columnsAutoResize"));
0246         QString alternatingRowColors2 = root.attribute(QStringLiteral("alternatingRowColors"));
0247         QString zoomPositionString = root.attribute(QStringLiteral("zoomPosition"));
0248         QString expandedGroups = root.attribute(QStringLiteral("expandedGroups"));
0249         stickH = (root.attribute(QStringLiteral("stickH")) == QStringLiteral("Y"));
0250         stickV = (root.attribute(QStringLiteral("stickV")) == QStringLiteral("Y"));
0251 
0252         // Set column order
0253         QStringList listAtt;
0254         if (!columns.isEmpty()) {
0255             listAtt = SKGServices::splitCSVLine(columns, ';');
0256             QStringList sizes = SKGServices::splitCSVLine(columnsSize, ';');
0257             QStringList visibilities = SKGServices::splitCSVLine(columnsVisibility, ';');
0258 
0259             int nb = listAtt.count();
0260             int nbvisibilities = visibilities.count();
0261             int nbsizes = sizes.count();
0262             for (int i = 0; i < nb; ++i) {
0263                 if (nbvisibilities == nb) {
0264                     listAtt[i] = listAtt.at(i) % '|' % visibilities.at(i);
0265                     if (nbsizes == nb) {
0266                         listAtt[i] = listAtt.at(i) % '|' % sizes.at(i);
0267                     }
0268                 }
0269             }
0270         }
0271         if (m_model != nullptr) {
0272             m_model->setSupportedAttributes(listAtt);
0273         }
0274 
0275         // Set autoResize
0276         if (!columnsAutoResize.isEmpty()) {
0277             m_autoResize = (columnsAutoResize == QStringLiteral("Y"));
0278             header()->setSectionResizeMode(m_autoResize ? QHeaderView::Fixed : QHeaderView::Interactive);
0279             if (!m_autoResize) {
0280                 m_timerDelayedResize.stop();
0281                 m_autoResizeDone = false;
0282             }
0283         }
0284 
0285         // Set sort
0286         if ((m_proxyModel != nullptr) && !sortPreviousColumn.isEmpty()) {
0287             m_proxyModel->setPreviousSortColumn(SKGServices::stringToInt(sortPreviousColumn));
0288         }
0289         if ((m_model != nullptr) && isSortingEnabled() && !sortOrder.isEmpty() && !sortColumn.isEmpty()) {
0290             int index = SKGServices::splitCSVLine(columns, ';').indexOf(sortColumn);
0291             if (index == -1) {
0292                 index = m_model->getIndexAttribute(sortColumn);
0293             }
0294             if (index == -1) {
0295                 index = 0;
0296             }
0297             qtsortorder = static_cast<Qt::SortOrder>(SKGServices::stringToInt(sortOrder));
0298             this->sortByColumn(index, qtsortorder);
0299         }
0300 
0301         if (m_model != nullptr) {
0302             QString att = m_groupby;
0303             if (att == QStringLiteral("#")) {
0304                 att = sortColumn;
0305             }
0306             m_model->setGroupBy(att);
0307             m_model->dataModified();
0308 
0309             refreshExpandCollapse();
0310         }
0311 
0312         // Set alternatingRowColors
0313         if (!alternatingRowColors2.isEmpty()) {
0314             setAlternatingRowColors(alternatingRowColors2 == QStringLiteral("Y"));
0315         }
0316         if (!zoomPositionString.isEmpty()) {
0317             setZoomPosition(SKGServices::stringToInt(zoomPositionString));
0318         }
0319 
0320         // Set expanded groups
0321         m_expandedNodes = SKGServices::splitCSVLine(expandedGroups);
0322         resetSelection();
0323     } else {
0324         if (m_model != nullptr) {
0325             m_model->setSupportedAttributes(QStringList());
0326             m_groupby = QLatin1String("");
0327             m_model->setGroupBy(m_groupby);
0328             m_model->dataModified();
0329 
0330             refreshExpandCollapse();
0331         }
0332 
0333         this->sortByColumn(0, qtsortorder);
0334     }
0335 }
0336 
0337 void SKGTreeView::refreshExpandCollapse()
0338 {
0339     bool treeMode = !m_model->getParentChildAttribute().isEmpty();
0340     setRootIsDecorated(treeMode && m_groupby.isEmpty());
0341     if (m_actExpandAll != nullptr) {
0342         m_actExpandAll->setVisible(treeMode || !m_groupby.isEmpty());
0343     }
0344     if (m_actCollapseAll != nullptr) {
0345         m_actCollapseAll->setVisible(treeMode || !m_groupby.isEmpty());
0346     }
0347 }
0348 
0349 void SKGTreeView::respanFirstColumns()
0350 {
0351     // Span groups
0352     int nbRow = m_model->rowCount();
0353     for (int row = 0; row < nbRow; ++row) {
0354         this -> setFirstColumnSpanned(row, QModelIndex(), !m_groupby.isEmpty());
0355     }
0356 
0357     if (m_autoResize) {
0358         resizeColumnsToContentsDelayed();
0359     }
0360 }
0361 
0362 void SKGTreeView::onRangeChanged()
0363 {
0364     auto* scroll2 = qobject_cast<QScrollBar*>(sender());
0365     if ((stickH && scroll2 == horizontalScrollBar()) || (stickV && scroll2 == verticalScrollBar())) {
0366         scroll2->setValue(scroll2->maximum());
0367     }
0368 }
0369 
0370 void SKGTreeView::onActionTriggered(int action)
0371 {
0372     auto* scroll2 = qobject_cast<QScrollBar*>(sender());
0373     if ((scroll2 != nullptr) && action == QAbstractSlider::SliderToMaximum) {
0374         if (scroll2 == horizontalScrollBar()) {
0375             stickH = true;
0376         }
0377         if (scroll2 == verticalScrollBar()) {
0378             stickV = true;
0379         }
0380     } else {
0381         if (scroll2 == horizontalScrollBar()) {
0382             stickH = false;
0383         }
0384         if (scroll2 == verticalScrollBar()) {
0385             stickV = false;
0386         }
0387     }
0388 }
0389 
0390 void SKGTreeView::insertGlobalAction(const QString& iRegisteredAction)
0391 {
0392     if (iRegisteredAction.isEmpty()) {
0393         auto sep = new QAction(this);
0394         sep->setSeparator(true);
0395         this->insertAction(nullptr, sep);
0396     } else if (SKGMainPanel::getMainPanel() != nullptr) {
0397         QAction* act = SKGMainPanel::getMainPanel()->getGlobalAction(iRegisteredAction);
0398         this->insertAction(nullptr, act);
0399     }
0400 }
0401 
0402 void SKGTreeView::resizeColumnsToContentsDelayed()
0403 {
0404     SKGTRACEINFUNC(10)
0405     m_timerDelayedResize.start(300);
0406 }
0407 
0408 void SKGTreeView::resizeColumnsToContents()
0409 {
0410     SKGTRACEINFUNC(10) {
0411         SKGTRACEIN(10, "SKGTreeView::resizeColumnsToContents-respanFirstColumns")
0412         auto save = m_autoResize;
0413         m_autoResize = false; // To avoid loop
0414         respanFirstColumns();
0415         m_autoResize = save;
0416     }
0417 
0418     int nb = header()->count();
0419     for (int i = nb - 1; i >= 0; --i) {
0420         SKGTRACEIN(10, "SKGTreeView::resizeColumnsToContents-resizeColumnToContents(" + SKGServices::intToString(i) + ')')
0421         if (!isColumnHidden(i)) {
0422             resizeColumnToContents(i);
0423         }
0424     }
0425 }
0426 
0427 void SKGTreeView::showHeaderMenu()
0428 {
0429     showHeaderMenu(header()->mapFromGlobal(QCursor::pos()));
0430 }
0431 
0432 void SKGTreeView::showHeaderMenu(const QPoint iPos)
0433 {
0434     if (m_headerMenu != nullptr) {
0435         m_headerMenu->popup(header()->mapToGlobal(iPos));
0436     }
0437 }
0438 
0439 void SKGTreeView::setDefaultSaveParameters(SKGDocument* iDocument, const QString& iParameterName)
0440 {
0441     m_document = iDocument;
0442     m_parameterName = iParameterName;
0443 }
0444 
0445 void SKGTreeView::setupHeaderMenu()
0446 {
0447     SKGTRACEINFUNC(10)
0448     if ((m_model != nullptr) && m_model->isRefreshBlocked()) {
0449         return;
0450     }
0451 
0452     setCornerWidget(nullptr);
0453     if ((m_model != nullptr) && (m_headerMenu != nullptr)) {
0454         // Corner button on tables to show the contextual menu
0455         auto btn = new QPushButton(this);
0456         btn->setIcon(SKGServices::fromTheme(QStringLiteral("configure")));
0457         btn->setFocusPolicy(Qt::NoFocus);
0458         connect(btn, &QPushButton::clicked, this, static_cast<void (SKGTreeView::*)()>(&SKGTreeView::showHeaderMenu));
0459         setCornerWidget(btn);
0460 
0461         m_headerMenu->clear();
0462 
0463         // Processing
0464         QMenu* columns = m_headerMenu->addMenu(i18nc("Noun, Menu name", "Columns"));
0465 
0466         // Get current groupby column
0467         QMenu* groupbymenu = nullptr;
0468         QActionGroup* groupby = nullptr;
0469         if (!m_model->getWhereClause().contains(QStringLiteral("ORDER BY"))) {
0470             groupbymenu = m_headerMenu->addMenu(i18nc("Noun, Menu name", "Group by"));
0471             groupby = new QActionGroup(groupbymenu);
0472             m_actGroupByNone = groupbymenu->addAction(i18nc("Noun, grouping option", "None"));
0473             if (m_actGroupByNone != nullptr) {
0474                 m_actGroupByNone->setIcon(SKGServices::fromTheme(QStringLiteral("dialog-cancel")));
0475                 m_actGroupByNone->setCheckable(true);
0476                 m_actGroupByNone->setChecked(m_groupby.isEmpty());
0477                 m_actGroupByNone->setData(QLatin1String(""));
0478                 groupby->addAction(m_actGroupByNone);
0479             }
0480             if (m_proxyModel != nullptr) {
0481                 QAction* actSort = groupbymenu->addAction(i18nc("Noun, grouping option", "Sorted column"));
0482                 if (actSort != nullptr) {
0483                     actSort->setIcon(SKGServices::fromTheme(QStringLiteral("view-sort-ascending")));
0484                     actSort->setCheckable(true);
0485                     actSort->setChecked(m_groupby == QStringLiteral("#"));
0486                     actSort->setData(QStringLiteral("#"));
0487                     groupby->addAction(actSort);
0488                 }
0489             }
0490             groupbymenu->addSeparator();
0491         }
0492 
0493         // Set right click menu
0494         SKGDocument::SKGModelTemplateList schemas = m_model->getSchemas();
0495         int nbSchemas = schemas.count();
0496         if (nbSchemas != 0) {
0497             QMenu* viewAppearanceMenu = columns->addMenu(SKGServices::fromTheme(QStringLiteral("view-file-columns")), i18nc("Noun, user action", "View appearance"));
0498 
0499             for (int i = 0; i < nbSchemas; ++i) {
0500                 SKGDocument::SKGModelTemplate schema = schemas.at(i);
0501                 QAction* act = viewAppearanceMenu->addAction(schema.name);
0502                 if (!schema.icon.isEmpty()) {
0503                     act->setIcon(SKGServices::fromTheme(schema.icon));
0504                 }
0505                 act->setData(schema.schema);
0506 
0507                 connect(act, &QAction::triggered, this, &SKGTreeView::changeSchema);
0508             }
0509         }
0510 
0511         QAction* actResize = columns->addAction(SKGServices::fromTheme(QStringLiteral("zoom-fit-width")), i18nc("Noun, user action", "Resize to content"));
0512         connect(actResize, &QAction::triggered, this, &SKGTreeView::resizeColumnsToContents);
0513 
0514         m_actAutoResize = columns->addAction(SKGServices::fromTheme(QStringLiteral("zoom-fit-width"), QStringList() << QStringLiteral("run-build")), i18nc("Noun, user action", "Auto resize"));
0515         m_actAutoResize->setCheckable(true);
0516         m_actAutoResize->setChecked(m_autoResize);
0517         connect(m_actAutoResize, &QAction::triggered, this, &SKGTreeView::switchAutoResize);
0518 
0519         QAction* actAlternatingRowColors = m_headerMenu->addAction(i18nc("Noun, user action", "Alternate row colors"));
0520         if (actAlternatingRowColors != nullptr) {
0521             actAlternatingRowColors->setCheckable(true);
0522             actAlternatingRowColors->setChecked(alternatingRowColors());
0523             connect(actAlternatingRowColors, &QAction::triggered, this, &SKGTreeView::setAlternatingRowColors);
0524         }
0525 
0526         if (m_document != nullptr) {
0527             QAction* actDefault = m_headerMenu->addAction(SKGServices::fromTheme(QStringLiteral("document-save")), i18nc("Noun, user action", "Save parameters"));
0528             connect(actDefault, &QAction::triggered, this, &SKGTreeView::saveDefaultClicked);
0529         }
0530 
0531         columns->addSeparator();
0532 
0533         // Build menus for columns
0534         QHeaderView* hHeader = header();
0535         int nbcol = hHeader->count();
0536         for (int i = 0; i < nbcol; ++i) {
0537             int idx = hHeader->logicalIndex(i);
0538             QString col = m_model->headerData(idx, Qt::Horizontal, Qt::UserRole).toString();
0539             QStringList values = col.split('|');
0540 
0541             if (!m_autoResizeDone) {
0542                 if (values.count() > 1) {
0543                     hHeader->setSectionHidden(idx, values.at(1) == QStringLiteral("N"));
0544                 }
0545                 if (values.count() > 2) {
0546                     auto s = SKGServices::stringToInt(values.at(2));
0547                     if (s > 0) {
0548                         hHeader->resizeSection(idx, s);
0549                     }
0550                 }
0551             }
0552 
0553             // Column menu
0554             QAction* act = columns->addAction(values.at(0));
0555             if (act != nullptr) {
0556                 act->setCheckable(true);
0557                 act->setChecked(!hHeader->isSectionHidden(idx));
0558                 act->setIcon(m_model->headerData(idx, Qt::Horizontal, Qt::DecorationRole).value<QIcon>());
0559                 act->setData(idx);
0560                 act->setEnabled(i > 0);
0561 
0562                 connect(act, &QAction::triggered, this, &SKGTreeView::showHideColumn);
0563 
0564                 // Group by menu
0565                 QString att = m_model->getAttribute(idx);
0566                 if ((groupbymenu != nullptr) && (groupby != nullptr)) {
0567                     QAction* act2 = groupbymenu->addAction(values.at(0));
0568                     if (act2 != nullptr) {
0569                         act2->setCheckable(true);
0570                         act2->setChecked(att == m_groupby);
0571                         act2->setIcon(act->icon());
0572                         act2->setData(att);
0573                         groupby->addAction(act2);
0574                     }
0575                 }
0576             }
0577         }
0578         m_autoResizeDone = true;
0579         if (groupby != nullptr) {
0580             connect(groupby, &QActionGroup::triggered, this, &SKGTreeView::groupByChanged);
0581         }
0582         m_headerMenu->addSeparator();
0583 
0584         QAction* actExport = m_headerMenu->addAction(SKGServices::fromTheme(QStringLiteral("document-export")), i18nc("Noun, user action", "Export…"));
0585         connect(actExport, &QAction::triggered, this, &SKGTreeView::onExport);
0586 
0587         if (m_autoResize) {
0588             resizeColumnsToContentsDelayed();
0589         }
0590     }
0591 }
0592 
0593 void SKGTreeView::setSelectionModel(QItemSelectionModel* iSelectionModel)
0594 {
0595     if (this->selectionModel() != nullptr) {
0596         disconnect(this->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SKGTreeView::onSelectionChanged);
0597     }
0598     QTreeView::setSelectionModel(iSelectionModel);
0599     if (iSelectionModel != nullptr) {
0600         connect(iSelectionModel, &QItemSelectionModel::selectionChanged, this, &SKGTreeView::onSelectionChanged);
0601     }
0602 }
0603 
0604 void SKGTreeView::onSelectionChanged()
0605 {
0606     SKGTRACEINFUNC(10)
0607     SKGObjectBase::SKGListSKGObjectBase selection;
0608     SKGObjectBase::SKGListSKGObjectBase selectionparents;
0609     QItemSelectionModel* selModel = selectionModel();
0610     if (selModel != nullptr) {
0611         if (m_model != nullptr) {
0612             auto indexes = selModel->selectedRows();
0613 
0614             int nb = indexes.count();
0615             QVector<QModelIndex> indexesToSource;
0616             indexesToSource.reserve(indexes.count());
0617             for (const auto& index : qAsConst(indexes)) {
0618                 indexesToSource.push_back(m_proxyModel != nullptr ? m_proxyModel->mapToSource(index) : index);
0619             }
0620 
0621             selection.reserve(indexesToSource.count());
0622             selectionparents.reserve(nb);
0623             for (int i = 0; i < nb; ++i) {
0624                 QModelIndex index = indexes.at(i);
0625                 QModelIndex idxs = indexesToSource.at(i);
0626 
0627                 // Get above
0628                 QModelIndex idxtmp = (m_proxyModel != nullptr ? m_proxyModel->mapToSource(indexAbove(index)) : indexAbove(index));
0629                 auto* tmp = m_model->getObjectPointer(idxtmp);
0630                 if (tmp != nullptr && idxtmp.parent() == idxs.parent() && !indexesToSource.contains(idxtmp)) {
0631                     selectionparents.push_back(*tmp);
0632                 } else {
0633                     // Get below
0634                     idxtmp = (m_proxyModel != nullptr ? m_proxyModel->mapToSource(indexBelow(index)) : indexBelow(index));
0635                     tmp = m_model->getObjectPointer(idxtmp);
0636                     if (tmp != nullptr && idxtmp.parent() == idxs.parent() && !indexesToSource.contains(idxtmp)) {
0637                         selectionparents.push_back(*tmp);
0638                     } else {
0639                         // Get parent
0640                         tmp = m_model->getObjectPointer(idxs.parent());
0641                         if (tmp != nullptr) {
0642                             selectionparents.push_back(*tmp);
0643                         }
0644                     }
0645                 }
0646 
0647                 SKGObjectBase obj = m_model->getObject(idxs);
0648                 selection.push_back(obj);
0649             }
0650         }
0651     }
0652 
0653 //    if (selection != m_lastSelection) {  // WARNING: selection can be equal but attributes of selected objects modified
0654     m_lastSelection = selection;
0655     m_lastSelection_if_deleted = selectionparents;
0656     m_timerSelectionChanged.start(300);
0657 //    }
0658 }
0659 
0660 void SKGTreeView::saveDefaultClicked()
0661 {
0662     if (m_document != nullptr) {
0663         SKGError err;
0664         SKGBEGINTRANSACTION(*m_document, i18nc("Noun, name of the user action", "Save default parameters"), err)
0665         err = m_document->setParameter(m_parameterName, getState());
0666     }
0667 }
0668 
0669 void SKGTreeView::switchAutoResize()
0670 {
0671     m_autoResize = m_actAutoResize->isChecked();
0672     header()->setSectionResizeMode(m_autoResize ? QHeaderView::Fixed : QHeaderView::Interactive);
0673     if (m_autoResize) {
0674         resizeColumnsToContentsDelayed();
0675     } else {
0676         m_timerDelayedResize.stop();
0677         m_autoResizeDone = false;
0678     }
0679 }
0680 
0681 void SKGTreeView::groupByChanged(QAction* iAction)
0682 {
0683     if ((m_model != nullptr) && m_model->isRefreshBlocked()) {
0684         return;
0685     }
0686 
0687     if ((iAction != nullptr) && (m_model != nullptr)) {
0688         m_groupby = iAction->data().toString();
0689         QString att = m_groupby;
0690         if (att == QStringLiteral("#") && (m_proxyModel != nullptr)) {
0691             att = m_model->getAttribute(m_proxyModel->sortColumn());
0692         }
0693         m_model->setGroupBy(att);
0694         m_model->refresh();
0695 
0696         refreshExpandCollapse();
0697         respanFirstColumns();
0698     }
0699 }
0700 
0701 void SKGTreeView::onSortChanged(int iIndex, Qt::SortOrder iOrder)
0702 {
0703     Q_UNUSED(iOrder)
0704     if (m_groupby == QStringLiteral("#") && (m_model != nullptr)) {
0705         m_model->setGroupBy(m_model->getAttribute(iIndex));
0706         m_model->refresh();
0707     }
0708 
0709     m_timerScrollSelection.start(300);
0710 }
0711 
0712 void SKGTreeView::showHideColumn()
0713 {
0714     auto* send = qobject_cast<QAction*>(this->sender());
0715     if (send != nullptr) {
0716         QHeaderView* hHeader = header();
0717 
0718         int idx = send->data().toInt();
0719         bool hidden = !hHeader->isSectionHidden(idx);
0720         hHeader->setSectionHidden(idx, hidden);
0721     }
0722 }
0723 
0724 void SKGTreeView::resetColumnsOrder()
0725 {
0726     QHeaderView* hHeader = header();
0727     int nbcol = hHeader->count();
0728     for (int i = 0; i < nbcol; ++i) {
0729         int idx = hHeader->visualIndex(i);
0730         if (idx != i) {
0731             hHeader->moveSection(idx, i);
0732         }
0733     }
0734 }
0735 
0736 void SKGTreeView::changeSchema()
0737 {
0738     QStringList list;
0739 
0740     auto* send = qobject_cast<QAction*>(this->sender());
0741     if (send != nullptr) {
0742         list = SKGServices::splitCSVLine(send->data().toString(), ';');
0743     }
0744 
0745     if (m_model != nullptr) {
0746         // Reset column oder
0747         resetColumnsOrder();
0748 
0749         m_model->setSupportedAttributes(list);
0750         bool tmp = m_autoResizeDone;
0751         m_autoResizeDone = false;
0752         m_model->dataModified();
0753         m_autoResizeDone = tmp;
0754         header()->setSortIndicator(0, Qt::AscendingOrder);
0755     }
0756 }
0757 
0758 QStringList SKGTreeView::getCurrentSchema() const
0759 {
0760     QStringList list;
0761     QHeaderView* hHeader = header();
0762     if ((hHeader != nullptr) && (m_model != nullptr)) {
0763         int nb = hHeader->count();
0764         if (nb != 0) {
0765             QString att;
0766             for (int i = 0; i < nb; ++i) {
0767                 int idx = hHeader->logicalIndex(i);
0768                 att = m_model->getAttribute(idx);
0769                 att += QStringLiteral("|") % (hHeader->isSectionHidden(idx) ? QStringLiteral("N") : QStringLiteral("Y"));
0770                 att += QStringLiteral("|") % SKGServices::intToString(hHeader->sectionSize(idx));
0771 
0772                 list.push_back(att);
0773             }
0774         }
0775     }
0776     return list;
0777 }
0778 void SKGTreeView::setAlternatingRowColors(bool enable)
0779 {
0780     QTreeView::setAlternatingRowColors(enable);
0781 }
0782 
0783 SKGObjectBase SKGTreeView::getFirstSelectedObject()
0784 {
0785     return m_lastSelection.value(0);
0786 }
0787 
0788 SKGObjectBase::SKGListSKGObjectBase SKGTreeView::getSelectedObjects()
0789 {
0790     return m_lastSelection;
0791 }
0792 
0793 int SKGTreeView::getNbSelectedObjects()
0794 {
0795     return m_lastSelection.count();
0796 }
0797 
0798 void SKGTreeView::saveSelection()
0799 {
0800     SKGTRACEINFUNC(10)
0801 
0802     m_selection.clear();
0803 
0804     SKGObjectBase::SKGListSKGObjectBase objs = getSelectedObjects();
0805     int nb = objs.count();
0806     // We save the selection only if not too big
0807     if (nb <= 100) {
0808         for (int i = 0; i < nb; ++i) {
0809             QString id = objs.at(i).getUniqueID();
0810             m_selection.push_back(id);
0811         }
0812     }
0813     SKGTRACEL(10) << m_selection.count() << " objects saved" << SKGENDL;
0814 }
0815 
0816 void SKGTreeView::selectObject(const QString& iUniqueID)
0817 {
0818     SKGTRACEINFUNC(10)
0819     QStringList tmp;
0820     tmp.push_back(iUniqueID);
0821     selectObjects(tmp, true);
0822 }
0823 
0824 void SKGTreeView::selectObjects(const QStringList& iUniqueIDs, bool iFocusOnFirstOne)
0825 {
0826     SKGTRACEINFUNC(10)
0827     SKGTRACEL(10) << iUniqueIDs.count() << " objects to select" << SKGENDL;
0828     int nbset = 0;
0829     QItemSelectionModel* selModel = selectionModel();
0830     if (selModel != nullptr) {
0831         bool previous = selModel->blockSignals(true);
0832 
0833         selModel->clearSelection();
0834 
0835         if (m_model != nullptr) {
0836             // Get all indexes
0837             QVector<QModelIndex> items;
0838             items.reserve(items.count() * 2);
0839             items.push_back(QModelIndex());
0840             for (int i = 0; i < items.count(); ++i) {  // Dynamic size because the list is modified
0841                 QModelIndex mi = items.at(i);
0842                 int nbRows = m_model->rowCount(mi);
0843                 for (int j = 0; j < nbRows; ++j) {
0844                     items.push_back(m_model->index(j, 0, mi));
0845                 }
0846             }
0847             items.removeAt(0);
0848 
0849             int nbRows = items.count();
0850             if (nbRows != 0) {
0851                 // Expand nodes
0852                 bool previousForThis = this->blockSignals(true);
0853                 for (int i = 0; i < nbRows; ++i) {
0854                     QModelIndex index = items.at(i);
0855                     SKGObjectBase obj = m_model->getObject(index);
0856                     QString id = obj.getUniqueID();
0857                     if (obj.getTable().isEmpty()) {
0858                         // This is a group
0859                         id = obj.getAttribute(QStringLiteral("t_title"));
0860                     }
0861                     if (m_expandedNodes.contains(id)) {
0862                         QModelIndex idxs = (m_proxyModel != nullptr ? m_proxyModel->mapFromSource(index) : index);
0863                         setExpanded(idxs, true);
0864                     }
0865                 }
0866                 this->blockSignals(previousForThis);
0867 
0868                 // Set selection
0869                 bool focusDone = false;
0870                 for (int i = 0; i < nbRows; ++i) {
0871                     QModelIndex index = items.at(i);
0872                     SKGObjectBase obj = m_model->getObject(index);
0873                     if (iUniqueIDs.contains(obj.getUniqueID())) {
0874                         QModelIndex idxs = (m_proxyModel != nullptr ? m_proxyModel->mapFromSource(index) : index);
0875                         selModel->select(idxs, QItemSelectionModel::Select | QItemSelectionModel::Rows);
0876                         selModel->setCurrentIndex(idxs, QItemSelectionModel::NoUpdate);
0877                         ++nbset;
0878                         if (iFocusOnFirstOne && !focusDone) {
0879                             scrollTo(idxs);
0880                             focusDone = true;
0881                         }
0882                     }
0883                 }
0884             }
0885         }
0886         selModel->blockSignals(previous);
0887     }
0888 
0889     SKGTRACEL(10) << nbset << " objects selected" << SKGENDL;
0890 
0891     onSelectionChanged();
0892 }
0893 
0894 void SKGTreeView::resetSelection()
0895 {
0896     SKGTRACEINFUNC(10)
0897 
0898     auto lastSelection_parents_save = m_lastSelection_if_deleted;  // because selectObjects will modify m_lastSelection_if_deleted
0899     selectObjects(m_selection);
0900     if ((lastSelection_parents_save.count() != 0) && (getSelectedObjects().count() == 0)) {
0901         // Try to select parent objects
0902         int nb = lastSelection_parents_save.count();
0903         // We save the selection only if not too big
0904         if (nb <= 100) {
0905             QStringList sel;
0906             sel.reserve(nb);
0907             for (int i = 0; i < nb; ++i) {
0908                 QString id = lastSelection_parents_save.at(i).getUniqueID();
0909                 sel.push_back(id);
0910             }
0911 
0912             selectObjects(sel);
0913         }
0914     }
0915 }
0916 
0917 void SKGTreeView::scroolOnSelection()
0918 {
0919     QItemSelectionModel* selModel = selectionModel();
0920     if (selModel != nullptr) {
0921         if (m_model != nullptr) {
0922             QModelIndexList indexes = selModel->selectedRows();
0923             if (!indexes.isEmpty()) {
0924                 scrollTo(indexes.at(0));
0925             }
0926         }
0927     }
0928 }
0929 
0930 void SKGTreeView::onExpand(const QModelIndex& index)
0931 {
0932     SKGTRACEINFUNC(10)
0933     if (index.isValid() && (m_model != nullptr)) {
0934         QModelIndex idxs = (m_proxyModel != nullptr ? m_proxyModel->mapToSource(index) : index);
0935 
0936         SKGObjectBase obj = m_model->getObject(idxs);
0937         QString id = obj.getUniqueID();
0938         if (obj.getTable().isEmpty()) {
0939             // This is a group
0940             id = obj.getAttribute(QStringLiteral("t_title"));
0941         }
0942         m_expandedNodes.push_back(id);
0943     }
0944 
0945     if (m_autoResize) {
0946         resizeColumnsToContentsDelayed();
0947     }
0948 }
0949 
0950 void SKGTreeView::expandAll()
0951 {
0952     SKGTRACEINFUNC(10)
0953     QTreeView::expandAll();
0954 
0955     if (m_autoResize) {
0956         resizeColumnsToContentsDelayed();
0957     }
0958 }
0959 
0960 void SKGTreeView::onCollapse(const QModelIndex& index)
0961 {
0962     SKGTRACEINFUNC(10)
0963     if (index.isValid() && (m_model != nullptr)) {
0964         QModelIndex idxs = (m_proxyModel != nullptr ? m_proxyModel->mapToSource(index) : index);
0965 
0966         SKGObjectBase obj = m_model->getObject(idxs);
0967 
0968         QString id = obj.getUniqueID();
0969         if (obj.getTable().isEmpty()) {
0970             // This is a group
0971             id = obj.getAttribute(QStringLiteral("t_title"));
0972         }
0973         m_expandedNodes.removeOne(id);
0974     }
0975 
0976     if (m_autoResize) {
0977         resizeColumnsToContentsDelayed();
0978     }
0979 }
0980 
0981 void SKGTreeView::onClick(const QModelIndex& index)
0982 {
0983     SKGTRACEINFUNC(10)
0984     if (index.isValid() && (m_actExpandAll != nullptr) && m_actExpandAll->isVisible()) {
0985         this->setExpanded(index, !this->isExpanded(index));
0986     }
0987 }
0988 
0989 void SKGTreeView::copy()
0990 {
0991     QItemSelectionModel* selection = selectionModel();
0992     if (selection != nullptr) {
0993         QModelIndexList indexes = selection->selectedIndexes();
0994 
0995         if (indexes.empty()) {
0996             return;
0997         }
0998 
0999         std::sort(indexes.begin(), indexes.end());
1000 
1001         QStringList colums_title;
1002         colums_title.reserve(indexes.count());
1003         SKGStringListList columns_values;
1004         for (const auto& current : qAsConst(indexes)) {
1005             auto header_title = model()->headerData(current.column(), Qt::Horizontal).toString();
1006             if (header_title.isEmpty()) {
1007                 header_title = SKGServices::splitCSVLine(model()->headerData(current.column(), Qt::Horizontal, Qt::UserRole).toString(), QLatin1Char('|')).at(0);
1008             }
1009 
1010             auto pos = colums_title.indexOf(header_title);
1011             if (pos == -1) {
1012                 colums_title.append(header_title);
1013                 colums_title.append(header_title + QLatin1String("_raw"));
1014 
1015                 columns_values.append(QStringList());
1016                 columns_values.append(QStringList());
1017 
1018                 pos = colums_title.count() - 2;
1019             }
1020 
1021             columns_values[pos].append(model()->data(current).toString());
1022             columns_values[pos + 1].append(model()->data(current, Qt::UserRole).toString());
1023         }
1024 
1025         // Remove useless raw columns (when all raw values = displayed values)
1026         int nbCols = columns_values.count();
1027         int nbRows = 0;
1028         if (nbCols > 0) {
1029             nbRows = columns_values.at(0).count();
1030             for (int c = nbCols - 1; c >= 0; c = c - 2) {
1031                 bool allEqual = true;
1032                 bool allDisplayedEmpty = true;
1033                 auto displayedValues = columns_values.at(c - 1);
1034                 auto rawValues = columns_values.at(c);
1035                 for (int r = 0; r < nbRows; ++r) {
1036                     if (!displayedValues.at(r).isEmpty()) {
1037                         allDisplayedEmpty = false;
1038                     }
1039                     if (rawValues.at(r) != displayedValues.at(r)) {
1040                         allEqual = false;
1041                     }
1042                 }
1043 
1044                 // Remove the raw column
1045                 if (allEqual) {
1046                     // Remove the useless raw column
1047                     colums_title.removeAt(c);
1048                     columns_values.removeAt(c);
1049                     nbCols--;
1050                 } else if (allDisplayedEmpty) {
1051                     // Remove the empty column and keep the raw version
1052                     colums_title.removeAt(c - 1);
1053                     columns_values.removeAt(c - 1);
1054                     nbCols--;
1055                 }
1056             }
1057         }
1058 
1059         // Build text
1060         QString text = colums_title.join(QLatin1Char(';')) + QLatin1Char('\n');
1061         if (nbCols > 0) {
1062             for (int r = 0; r < nbRows; ++r) {
1063                 for (int c = 0; c < nbCols; ++c) {
1064                     if (c != 0) {
1065                         text += QLatin1Char(';');
1066                     }
1067                     text += columns_values.at(c).at(r);
1068                 }
1069                 text += QLatin1Char('\n');
1070             }
1071         }
1072 
1073         auto clipBoard = QApplication::clipboard();
1074         if (clipBoard != nullptr) {
1075             clipBoard->setText(text);
1076         }
1077     }
1078 }
1079 
1080 void SKGTreeView::setZoomPosition(int iZoomPosition)
1081 {
1082     int newZoomPos = qMax(qMin(iZoomPosition, 10), -10);
1083     if (newZoomPos != zoomPosition() && m_fontOriginalPointSize + newZoomPos > 1) {
1084         QFont newFont = this->font();
1085         newFont.setPointSize(m_fontOriginalPointSize + newZoomPos);
1086         int newIconSize = qMax(m_iconOriginalSize + newZoomPos, 1);
1087 
1088         this->setFont(newFont);
1089         this->setIconSize(QSize(newIconSize, newIconSize));
1090         header()->setIconSize(QSize(newIconSize, newIconSize));
1091 
1092         if (m_autoResize) {
1093             resizeColumnsToContentsDelayed();
1094         }
1095 
1096         Q_EMIT zoomChanged(newZoomPos);
1097     }
1098 }
1099 
1100 int SKGTreeView::zoomPosition()
1101 {
1102     return this->font().pointSize() - m_fontOriginalPointSize;
1103 }
1104 
1105 bool SKGTreeView::eventFilter(QObject* iObject, QEvent* iEvent)
1106 {
1107     if (iObject == this && iEvent != nullptr && iEvent->type() == QEvent::Wheel) {
1108         auto* e = dynamic_cast<QWheelEvent*>(iEvent);
1109         if (m_textResizable && (e != nullptr) && ((QApplication::keyboardModifiers() &Qt::ControlModifier) != 0u)) {
1110             int numDegrees = e->angleDelta().y() / 8;
1111             int numTicks = numDegrees / 15;
1112 
1113             setZoomPosition(zoomPosition() + (numTicks > 0 ? 1 : -1));
1114             e->setAccepted(true);
1115             return true;
1116         }
1117     }
1118     if (iObject == this && iEvent != nullptr && iEvent->type() == QEvent::KeyPress) {
1119         auto* kevent = dynamic_cast<QKeyEvent*>(iEvent);
1120         if (kevent != nullptr) {
1121             if (kevent->matches(QKeySequence::Copy) && this->state() != QAbstractItemView::EditingState) {
1122                 copy();
1123                 if (iEvent != nullptr) {
1124                     iEvent->accept();
1125                 }
1126                 return true;  // stop the process
1127             }
1128         }
1129     }
1130     return QTreeView::eventFilter(iObject, iEvent);
1131 }
1132 
1133 void SKGTreeView::mousePressEvent(QMouseEvent* iEvent)
1134 {
1135     if ((iEvent != nullptr) && iEvent->button() == Qt::LeftButton && !(this->indexAt(iEvent->pos()).isValid())) {
1136         Q_EMIT clickEmptyArea();
1137         clearSelection();
1138     }
1139 
1140     if ((iEvent != nullptr) && iEvent->button() == Qt::LeftButton && (m_proxyModel != nullptr) && (m_model != nullptr)) {
1141         int propertyUUID = m_proxyModel->data(indexAt(iEvent->pos()), 101).toInt();
1142         if (propertyUUID != 0) {
1143             SKGPropertyObject prop(m_model->getDocument(), propertyUUID);
1144             QDesktopServices::openUrl(prop.getUrl(true));
1145         }
1146     }
1147 
1148     QTreeView::mousePressEvent(iEvent);
1149 }
1150 
1151 bool SKGTreeView::isTextResizable() const
1152 {
1153     return m_textResizable;
1154 }
1155 
1156 void SKGTreeView::setTextResizable(bool resizable)
1157 {
1158     if (m_textResizable != resizable) {
1159         m_textResizable = resizable;
1160         Q_EMIT modified();
1161     }
1162 }
1163 
1164 QTextBrowser* SKGTreeView::getTextBrowser() const
1165 {
1166     auto output = new QTextBrowser();
1167     QTextCursor tcursor = output->textCursor();
1168     tcursor.beginEditBlock();
1169 
1170     // Create table format
1171     QTextTableFormat tableFormat;
1172     tableFormat.setAlignment(Qt::AlignHCenter);
1173     tableFormat.setAlignment(Qt::AlignLeft);
1174     tableFormat.setBackground(QColor(255, 255, 255));
1175     tableFormat.setCellPadding(5);
1176     tableFormat.setCellSpacing(5);
1177 
1178     // Create table
1179     SKGStringListList table = getTable();
1180     int nbRows = table.count();
1181     int nbCol = table.at(0).count();
1182 
1183     QTextTable* tableau = tcursor.insertTable(nbRows, nbCol, tableFormat);
1184 
1185     // Create frame
1186     QTextFrame* frame = tcursor.currentFrame();
1187     QTextFrameFormat frameFormat = frame->frameFormat();
1188     frameFormat.setBorder(0);
1189     frame->setFrameFormat(frameFormat);
1190 
1191     // Create header table format
1192     QTextCharFormat headerFormat;
1193     headerFormat.setFontPointSize(6);
1194     headerFormat.setFontWeight(QFont::Bold);
1195 
1196     // Create text format
1197     QTextCharFormat textFormat;
1198     textFormat.setFontPointSize(6);
1199 
1200     // Create header
1201     for (int r = 0; r < nbRows; ++r) {
1202         const QStringList& line = table.at(r);
1203         for (int c = 0 ; c < nbCol ; ++c) {
1204             QTextCursor cellCursor = tableau->cellAt(r, c).firstCursorPosition();
1205             cellCursor.insertText(line.at(c), (r == 0 ? headerFormat : textFormat));
1206         }
1207     }
1208 
1209     // End
1210     tcursor.endEditBlock();
1211 
1212     return output;
1213 }
1214 
1215 SKGStringListList SKGTreeView::getTable(const QModelIndex& iIndex) const
1216 {
1217     // Build table
1218     SKGStringListList table;
1219 
1220     // Get header names
1221     if (m_model != nullptr) {
1222         // Header
1223         int nb = m_model->columnCount();
1224         int nb2 = m_model->rowCount(iIndex);
1225         table.reserve(1 + nb2 * 2);
1226         if (!iIndex.isValid()) {
1227             QStringList cols;
1228             cols.reserve(nb);
1229             for (int i = 0; i < nb; ++i) {
1230                 cols.append(m_model->headerData(i, Qt::Horizontal, Qt::UserRole).toString().split('|').at(0));
1231             }
1232             table.append(cols);
1233         }
1234 
1235         // Get content
1236         for (int i = 0; i < nb2; ++i) {
1237             QStringList row;
1238             row.reserve(nb);
1239             for (int j = 0; j < nb; j++) {
1240                 // We have to check the type for 214849
1241                 QModelIndex idx = m_model->index(i, j, iIndex);
1242 
1243                 SKGServices::AttributeType type = m_model->getAttributeType(j);
1244                 QString display = m_model->data(idx, type == SKGServices::FLOAT || m_model->getObject(idx).getTable().isEmpty() ?  Qt::DisplayRole : Qt::UserRole).toString();
1245                 if (display.isEmpty()) {
1246                     display = m_model->data(idx, Qt::DisplayRole).toString();
1247                 }
1248                 row.append(display);
1249             }
1250 
1251             table.append(row);
1252 
1253             QModelIndex idx0 = m_model->index(i, 0, iIndex);
1254             if (m_model->hasChildren(idx0)) {
1255                 table.append(getTable(idx0));
1256             }
1257         }
1258     }
1259     return table;
1260 }
1261 
1262 SKGError SKGTreeView::exportInFile(const QString& iFileName)
1263 {
1264     SKGError err;
1265     _SKGTRACEINFUNC(10)
1266     QString codec = QTextCodec::codecForLocale()->name();
1267     QString extension = QFileInfo(iFileName).suffix().toUpper();
1268     if (extension == QStringLiteral("CSV")) {
1269         // Write file
1270         QSaveFile file(iFileName);
1271         if (!file.open(QIODevice::WriteOnly)) {
1272             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
1273         } else {
1274             QTextStream out(&file);
1275             out.setCodec(codec.toLatin1().constData());
1276             QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_CSV);
1277             int nbl = dump.count();
1278             for (int i = 0; i < nbl; ++i) {
1279                 out << dump.at(i) << SKGENDL;
1280             }
1281 
1282             // Close file
1283             file.commit();
1284         }
1285     } else if (extension == QStringLiteral("PDF")) {
1286         QImage image(this->size(), QImage::Format_ARGB32);
1287         QPainter painter(&image);
1288         this->render(&painter);
1289         painter.end();
1290 
1291         {
1292             QPrinter printer(QPrinter::HighResolution);
1293             printer.setOutputFileName(iFileName);
1294             QPainter newPainter(&printer);
1295 
1296             QRect painterRect = newPainter.viewport();
1297             QSize imageSize = image.size();
1298             imageSize.scale(painterRect.size(), Qt::KeepAspectRatio);
1299             newPainter.setViewport(painterRect.x(), painterRect.y(), imageSize.width(), imageSize.height());
1300             newPainter.setWindow(image.rect());
1301             newPainter.drawImage(0, 0, image);
1302             newPainter.end();
1303         }
1304     } else if (extension == QStringLiteral("SVG")) {
1305         QSvgGenerator generator;
1306         generator.setFileName(iFileName);
1307         generator.setTitle(i18nc("Title of the content SVG export", "Skrooge SVG export"));
1308         generator.setDescription(i18nc("Description of the content SVG export", "A SVG drawing created by the Skrooge."));
1309 
1310         QPainter painter(&generator);
1311         QWidget* w = this->viewport();
1312         w->render(&painter);
1313         generator.setSize(QSize(w->widthMM(), w->heightMM()));
1314         generator.setViewBox(QRect(0, 0, w->widthMM(), w->heightMM()));
1315 
1316         painter.end();
1317     } else if (extension == QStringLiteral("HTML")) {
1318         // Write file
1319         QSaveFile file(iFileName);
1320         if (!file.open(QIODevice::WriteOnly)) {
1321             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
1322         } else {
1323             QTextStream out(&file);
1324             out.setCodec(codec.toLatin1().constData());
1325             QTextBrowser* tb = getTextBrowser();
1326             if (tb != nullptr) {
1327                 out << tb->toHtml().replace(QStringLiteral("<meta name=\"qrichtext\" content=\"1\" />"), QStringLiteral("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />")) << SKGENDL;
1328 
1329                 delete tb;
1330             }
1331 
1332             // Close file
1333             file.commit();
1334         }
1335     } else if (extension == QStringLiteral("ODT")) {
1336         QTextBrowser* tb = getTextBrowser();
1337         if (tb != nullptr) {
1338             QTextDocument doc;
1339             doc.setHtml(tb->toHtml());
1340 
1341             QTextDocumentWriter docWriter(iFileName);
1342             docWriter.write(&doc);
1343 
1344             delete tb;
1345         }
1346     } else {
1347         // Write file
1348         QSaveFile file(iFileName);
1349         if (!file.open(QIODevice::WriteOnly)) {
1350             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
1351         } else {
1352             QTextStream out(&file);
1353             out.setCodec(codec.toLatin1().constData());
1354             QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_TEXT);
1355             int nbl = dump.count();
1356             for (int i = 0; i < nbl; ++i) {
1357                 out << dump.at(i) << SKGENDL;
1358             }
1359 
1360             // Close file
1361             file.commit();
1362         }
1363     }
1364 
1365     return err;
1366 }
1367 
1368 void SKGTreeView::onExport()
1369 {
1370     _SKGTRACEINFUNC(10)
1371     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);
1372     if (!fileName.isEmpty()) {
1373         SKGError err = exportInFile(fileName);
1374         SKGMainPanel::displayErrorMessage(err);
1375         QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
1376     }
1377 }
1378 
1379 void SKGTreeView::setModel(QAbstractItemModel* iModel)
1380 {
1381     if (iModel != this->model()) {
1382         m_model = qobject_cast<SKGObjectModelBase*>(iModel);
1383         m_proxyModel = qobject_cast<SKGSortFilterProxyModel*> (iModel);
1384         if (m_proxyModel != nullptr) {
1385             m_model = qobject_cast<SKGObjectModelBase*>(m_proxyModel->sourceModel());
1386         }
1387 
1388         if (m_model != nullptr) {
1389             connect(m_model, &SKGObjectModelBase::afterReset, this, &SKGTreeView::setupHeaderMenu);
1390             // connect(m_model, &SKGObjectModelBase::afterReset, this, &SKGTreeView::onSelectionChanged);
1391             connect(m_model, &SKGObjectModelBase::afterReset, this, &SKGTreeView::respanFirstColumns, Qt::QueuedConnection);
1392         }
1393         QTreeView::setModel(iModel);
1394 
1395         rebuildContextualMenu();
1396         refreshExpandCollapse();
1397     }
1398 }
1399 
1400 QMenu* SKGTreeView::getHeaderMenu() const
1401 {
1402     return m_headerMenu;
1403 }
1404 
1405 void SKGTreeView::rebuildContextualMenu()
1406 {
1407     // Remove all Actions
1408     const auto list = actions();
1409     for (auto act : list) {
1410         removeAction(act);
1411     }
1412 
1413     if (selectionMode() != NoSelection) {
1414         // Build contextual menu
1415         this->insertAction(nullptr, m_actCopy);
1416         this->insertAction(nullptr, m_actExpandAll);
1417         this->insertAction(nullptr, m_actCollapseAll);
1418 
1419         if ((m_model != nullptr) && (SKGMainPanel::getMainPanel() != nullptr)) {
1420             const auto list = SKGMainPanel::getMainPanel()->getActionsForContextualMenu(m_model->getRealTable());
1421             for (const auto& act : list) {
1422                 if (act == nullptr) {
1423                     insertGlobalAction();
1424                 } else {
1425                     insertAction(nullptr, act);
1426                 }
1427             }
1428         }
1429     }
1430 }