File indexing completed on 2024-05-12 03:48:28

0001 /*
0002     File                 : SpreadsheetView.cpp
0003     Project              : LabPlot
0004     Description          : View class for Spreadsheet
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2011-2023 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2016 Fabian Kristof <fkristofszabolcs@gmail.com>
0008     SPDX-FileCopyrightText: 2020-2023 Stefan Gerlach <stefan.gerlach@uni.kn>
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "SpreadsheetView.h"
0013 #include "backend/core/Project.h"
0014 #include "backend/core/Settings.h"
0015 #include "backend/core/column/Column.h"
0016 #include "backend/core/datatypes/DateTime2StringFilter.h"
0017 #include "backend/core/datatypes/Double2StringFilter.h"
0018 #include "backend/core/datatypes/SimpleCopyThroughFilter.h"
0019 #include "backend/core/datatypes/String2DateTimeFilter.h"
0020 #include "backend/core/datatypes/String2DoubleFilter.h"
0021 #include "backend/datasources/filters/FITSFilter.h"
0022 #include "backend/datasources/filters/XLSXFilter.h"
0023 #include "backend/lib/macros.h"
0024 #include "backend/lib/trace.h"
0025 #include "backend/spreadsheet/Spreadsheet.h"
0026 #include "backend/spreadsheet/SpreadsheetModel.h"
0027 #include "backend/spreadsheet/StatisticsSpreadsheet.h"
0028 #include "backend/worksheet/plots/cartesian/BoxPlot.h" //TODO: needed for the icon only, remove later once we have a breeze icon
0029 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0030 #include "commonfrontend/spreadsheet/SpreadsheetHeaderView.h"
0031 #include "commonfrontend/spreadsheet/SpreadsheetItemDelegate.h"
0032 
0033 #include "kdefrontend/spreadsheet/AddSubtractValueDialog.h"
0034 #include "kdefrontend/spreadsheet/DropValuesDialog.h"
0035 #include "kdefrontend/spreadsheet/EquidistantValuesDialog.h"
0036 #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h"
0037 #include "kdefrontend/spreadsheet/FlattenColumnsDialog.h"
0038 #include "kdefrontend/spreadsheet/FormattingHeatmapDialog.h"
0039 #include "kdefrontend/spreadsheet/FunctionValuesDialog.h"
0040 #include "kdefrontend/spreadsheet/GoToDialog.h"
0041 #include "kdefrontend/spreadsheet/PlotDataDialog.h"
0042 #include "kdefrontend/spreadsheet/RandomValuesDialog.h"
0043 #include "kdefrontend/spreadsheet/RescaleDialog.h"
0044 #include "kdefrontend/spreadsheet/SampleValuesDialog.h"
0045 #include "kdefrontend/spreadsheet/SearchReplaceWidget.h"
0046 #include "kdefrontend/spreadsheet/SortDialog.h"
0047 #include "kdefrontend/spreadsheet/StatisticsDialog.h"
0048 
0049 #ifdef Q_OS_MAC
0050 #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h"
0051 #endif
0052 
0053 #include <KConfigGroup>
0054 #include <KLocalizedString>
0055 #include <KMessageBox>
0056 
0057 #include <QAbstractSlider>
0058 #include <QActionGroup>
0059 #include <QApplication>
0060 #include <QClipboard>
0061 #include <QDate>
0062 #include <QFile>
0063 #include <QInputDialog>
0064 #include <QKeyEvent>
0065 #include <QMenu>
0066 #include <QMessageBox>
0067 #include <QMimeData>
0068 #include <QPainter>
0069 #include <QPrintDialog>
0070 #include <QPrintPreviewDialog>
0071 #include <QPrinter>
0072 #include <QProcess>
0073 #include <QRandomGenerator>
0074 #include <QRegularExpression>
0075 #include <QScrollBar>
0076 #include <QSqlDatabase>
0077 #include <QSqlError>
0078 #include <QSqlQuery>
0079 #include <QTableView>
0080 #include <QTextStream>
0081 #include <QTimer>
0082 #include <QToolBar>
0083 #include <QUndoCommand>
0084 
0085 #include <algorithm> //for std::reverse
0086 
0087 #include <gsl/gsl_const_cgs.h>
0088 #include <gsl/gsl_math.h>
0089 
0090 enum NormalizationMethod {
0091     DivideBySum,
0092     DivideByMin,
0093     DivideByMax,
0094     DivideByCount,
0095     DivideByMean,
0096     DivideByMedian,
0097     DivideByMode,
0098     DivideByRange,
0099     DivideBySD,
0100     DivideByMAD,
0101     DivideByIQR,
0102     ZScoreSD,
0103     ZScoreMAD,
0104     ZScoreIQR,
0105     Rescale
0106 };
0107 
0108 enum TukeyLadderPower { InverseSquared, Inverse, InverseSquareRoot, Log, SquareRoot, Squared, Cube };
0109 
0110 /*!
0111     \class SpreadsheetView
0112     \brief View class for Spreadsheet
0113 
0114     \ingroup commonfrontend
0115  */
0116 SpreadsheetView::SpreadsheetView(Spreadsheet* spreadsheet, bool readOnly)
0117     : QWidget()
0118     , m_tableView(new QTableView(this))
0119     , m_spreadsheet(spreadsheet)
0120     , m_readOnly(readOnly) {
0121     auto* layout = new QVBoxLayout(this);
0122     layout->setContentsMargins(0, 0, 0, 0);
0123     layout->addWidget(m_tableView);
0124     if (m_readOnly)
0125         m_tableView->setEditTriggers(QTableView::NoEditTriggers);
0126 
0127     init();
0128 
0129     // resize the view to show alls columns and the first 10 rows.
0130     // no need to resize the view when the project is being opened,
0131     // all views will be resized to the stored values at the end
0132     if (!m_spreadsheet->isLoading()) {
0133         int w = m_tableView->verticalHeader()->width();
0134         int h = m_horizontalHeader->height();
0135         for (int i = 0; i < m_horizontalHeader->count(); ++i)
0136             w += m_horizontalHeader->sectionSize(i);
0137 
0138         if (m_tableView->verticalHeader()->count() <= 10)
0139             h += m_tableView->verticalHeader()->sectionSize(0) * m_tableView->verticalHeader()->count();
0140         else
0141             h += m_tableView->verticalHeader()->sectionSize(0) * 11;
0142 
0143         resize(w + 50, h);
0144     }
0145 
0146     KConfigGroup group = Settings::group(QStringLiteral("Spreadsheet"));
0147     showComments(group.readEntry(QLatin1String("ShowComments"), false));
0148 }
0149 
0150 SpreadsheetView::~SpreadsheetView() {
0151 }
0152 
0153 void SpreadsheetView::init() {
0154     // create a new SpreadsheetModel if not available yet.
0155     // the creation of the model is done here since it's only required
0156     // for the view but its lifecycle is managed in Spreadsheet,
0157     // i.e. the deletion of the model is done in the destructor of Spreadsheet
0158     m_model = m_spreadsheet->model();
0159     if (!m_model)
0160         m_model = new SpreadsheetModel(m_spreadsheet);
0161 
0162     m_tableView->setModel(m_model);
0163     auto* delegate = new SpreadsheetItemDelegate(this);
0164     connect(delegate, &SpreadsheetItemDelegate::returnPressed, this, &SpreadsheetView::advanceCell);
0165     connect(delegate, &SpreadsheetItemDelegate::editorEntered, this, [=]() {
0166         //      action_insert_row_below->setShortcut(QKeySequence());
0167         m_editorEntered = true;
0168     });
0169     connect(delegate, &SpreadsheetItemDelegate::closeEditor, this, [=]() {
0170         //      action_insert_row_below->setShortcut(Qt::Key_Insert);
0171         m_editorEntered = false;
0172     });
0173 
0174     m_tableView->setItemDelegate(delegate);
0175     m_tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0176 
0177     // horizontal header
0178     m_horizontalHeader = new SpreadsheetHeaderView(this);
0179     m_horizontalHeader->setSectionsClickable(true);
0180     m_horizontalHeader->setHighlightSections(true);
0181     m_tableView->setHorizontalHeader(m_horizontalHeader);
0182     m_horizontalHeader->setSectionsMovable(true);
0183     m_horizontalHeader->installEventFilter(this);
0184     m_tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0185     //  m_tableView->installEventFilter(this);
0186 
0187     resizeHeader();
0188 
0189     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionMoved, this, &SpreadsheetView::handleHorizontalSectionMoved);
0190     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionDoubleClicked, this, &SpreadsheetView::handleHorizontalHeaderDoubleClicked);
0191     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionResized, this, &SpreadsheetView::handleHorizontalSectionResized);
0192     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionClicked, this, &SpreadsheetView::columnClicked);
0193 
0194     // vertical header
0195     QHeaderView* v_header = m_tableView->verticalHeader();
0196     v_header->setSectionResizeMode(QHeaderView::Fixed);
0197     v_header->setSectionsMovable(false);
0198     v_header->installEventFilter(this);
0199     m_tableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0200 
0201     setFocusPolicy(Qt::StrongFocus);
0202     setFocus();
0203     installEventFilter(this);
0204     showComments(false);
0205 
0206     connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::updateHeaderGeometry);
0207     connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::handleHeaderDataChanged);
0208     connect(m_spreadsheet, &Spreadsheet::aspectsInserted, this, &SpreadsheetView::handleAspectsAdded);
0209     connect(m_spreadsheet, &Spreadsheet::aspectsAboutToBeRemoved, this, &SpreadsheetView::handleAspectAboutToBeRemoved);
0210     connect(m_spreadsheet, &Spreadsheet::requestProjectContextMenu, this, &SpreadsheetView::createContextMenu);
0211     connect(m_spreadsheet, &Spreadsheet::manyAspectsAboutToBeInserted, [this] {
0212         m_suppressResize = true;
0213     });
0214 
0215     // selection related connections
0216     connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged);
0217     connect(m_spreadsheet, &Spreadsheet::columnSelected, this, &SpreadsheetView::selectColumn);
0218     connect(m_spreadsheet, &Spreadsheet::columnDeselected, this, &SpreadsheetView::deselectColumn);
0219 }
0220 
0221 /*!
0222     set the column sizes to the saved values or resize to content if no size was saved yet
0223 */
0224 void SpreadsheetView::resizeHeader() {
0225     DEBUG(Q_FUNC_INFO)
0226     const auto& columns = m_spreadsheet->children<Column>();
0227 
0228     QFontMetrics fontMetrics(m_horizontalHeader->font());
0229     const auto* style = m_horizontalHeader->style();
0230     int headerOffset = style->pixelMetric(QStyle::PM_SmallIconSize, nullptr, m_horizontalHeader); // icon size
0231     headerOffset += 3 * style->pixelMetric(QStyle::PM_HeaderMargin, nullptr, m_horizontalHeader); // two margins plus the margin between icon and text
0232 
0233     int c = 0;
0234     for (auto col : columns) {
0235         if (col->width() == 0) {
0236             // No width was saved yet, resize to fit the content:
0237             // Calling m_tableView->resizeColumnToContents(i) is expensive since Qt needs to iterate over all values in the column
0238             // and to determine the maximal length which takes time the more rows we have in the spreadsheet. For a high number of
0239             // columns in the spreadsheet this operation can take significant amount of time, s. a. BUG: 455977.
0240             // To improve the performance, we check the width of the header texts only and resize the column widths to fit the header
0241             // widths. There will be cases where this is not a perfect fit but the user still can resize manually if needed.
0242             // Since this approach doesn't always lead to precise results (text width in other cells in the column can be bigger) and
0243             // since in many cases we deal with much lower number of columns during the import, we apply the more precise method with
0244             // resizeColumnToContents() if the number of columns is smaller than 50.
0245             if (columns.count() > 50) {
0246                 int width = headerOffset + fontMetrics.horizontalAdvance(m_model->headerData(c, Qt::Horizontal, Qt::DisplayRole).toString());
0247                 m_tableView->setColumnWidth(c, width);
0248             } else
0249                 m_tableView->resizeColumnToContents(c);
0250         } else
0251             m_tableView->setColumnWidth(c, col->width());
0252 
0253         c++;
0254     }
0255 }
0256 
0257 void SpreadsheetView::setFocus() {
0258     m_tableView->setFocus();
0259 }
0260 
0261 void SpreadsheetView::resizeEvent(QResizeEvent* event) {
0262     QWidget::resizeEvent(event);
0263     if (m_frozenTableView)
0264         updateFrozenTableGeometry();
0265 }
0266 
0267 void SpreadsheetView::updateFrozenTableGeometry() {
0268     m_frozenTableView->setGeometry(m_tableView->verticalHeader()->width() + m_tableView->frameWidth(),
0269                                    m_tableView->frameWidth(),
0270                                    m_tableView->columnWidth(0),
0271                                    m_tableView->viewport()->height() + m_tableView->horizontalHeader()->height());
0272 }
0273 
0274 void SpreadsheetView::initActions() {
0275     // selection related actions
0276     action_cut_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18n("Cu&t"), this);
0277     action_copy_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy"), this);
0278     action_paste_into_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Past&e"), this);
0279     action_mask_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-node")), i18n("&Mask"), this);
0280     action_unmask_selection = new QAction(QIcon::fromTheme(QStringLiteral("format-remove-node")), i18n("&Unmask"), this);
0281     action_clear_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clea&r Content"), this);
0282     action_select_all = new QAction(QIcon::fromTheme(QStringLiteral("edit-select-all")), i18n("Select All"), this);
0283 
0284     //  action_set_formula = new QAction(QIcon::fromTheme(QString()), i18n("Assign &Formula"), this);
0285     //  action_recalculate = new QAction(QIcon::fromTheme(QString()), i18n("Recalculate"), this);
0286     //  action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this);
0287     action_fill_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this);
0288     action_fill_random = new QAction(QIcon::fromTheme(QString()), i18n("Uniform Random Values"), this);
0289     action_fill_random_nonuniform = new QAction(QIcon::fromTheme(QString()), i18n("Random Values"), this);
0290     action_fill_equidistant = new QAction(QIcon::fromTheme(QString()), i18n("Equidistant Values"), this);
0291     action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this);
0292     action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this);
0293 
0294     action_sample_values = new QAction(QIcon::fromTheme(QStringLiteral("view-list-details")), i18n("Sample Values"), this);
0295     action_flatten_columns = new QAction(QIcon::fromTheme(QStringLiteral("gnumeric-object-list")), i18n("Flatten Columns"), this);
0296 
0297     // spreadsheet related actions
0298     action_toggle_comments = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Show Comments"), this);
0299     action_clear_spreadsheet = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Spreadsheet"), this);
0300     action_clear_masks = new QAction(QIcon::fromTheme(QStringLiteral("format-remove-node")), i18n("Clear Masks"), this);
0301     action_go_to_cell = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("&Go to Cell..."), this);
0302     action_search = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Search"), this);
0303     action_search->setShortcut(QKeySequence::Find);
0304     action_search_replace = new QAction(QIcon::fromTheme(QStringLiteral("edit-find-replace")), i18n("&Replace"), this);
0305     action_search_replace->setShortcut(QKeySequence::Replace);
0306     action_statistics_all_columns = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Column Statistics..."), this);
0307 
0308     action_statistics_spreadsheet = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Column Statistics Spreadsheet"), this);
0309     action_statistics_spreadsheet->setCheckable(true);
0310     bool hasStatisticsSpreadsheet = (m_spreadsheet->children<StatisticsSpreadsheet>().size() == 1);
0311     action_statistics_spreadsheet->setChecked(hasStatisticsSpreadsheet);
0312 
0313     // column related actions
0314     action_insert_column_left = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-left")), i18n("Insert Column Left"), this);
0315     action_insert_column_right = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-right")), i18n("Insert Column Right"), this);
0316     action_insert_columns_left = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-left")), i18n("Insert Multiple Columns Left"), this);
0317     action_insert_columns_right = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-right")), i18n("Insert Multiple Columns Right"), this);
0318     action_remove_columns = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-column")), i18n("Delete Selected Column(s)"), this);
0319     action_clear_columns = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Content"), this);
0320     action_freeze_columns = new QAction(i18n("Freeze Column"), this);
0321 
0322     // TODO: action collection?
0323     action_set_as_none = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::NoDesignation, false), this);
0324     action_set_as_none->setData(static_cast<int>(AbstractColumn::PlotDesignation::NoDesignation));
0325 
0326     action_set_as_x = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::X, false), this);
0327     action_set_as_x->setData(static_cast<int>(AbstractColumn::PlotDesignation::X));
0328 
0329     action_set_as_y = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::Y, false), this);
0330     action_set_as_y->setData(static_cast<int>(AbstractColumn::PlotDesignation::Y));
0331 
0332     action_set_as_z = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::Z, false), this);
0333     action_set_as_z->setData(static_cast<int>(AbstractColumn::PlotDesignation::Z));
0334 
0335     action_set_as_xerr = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::XError, false), this);
0336     action_set_as_xerr->setData(static_cast<int>(AbstractColumn::PlotDesignation::XError));
0337 
0338     action_set_as_xerr_plus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::XErrorPlus, false), this);
0339     action_set_as_xerr_plus->setData(static_cast<int>(AbstractColumn::PlotDesignation::XErrorPlus));
0340 
0341     action_set_as_xerr_minus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::XErrorMinus, false), this);
0342     action_set_as_xerr_minus->setData(static_cast<int>(AbstractColumn::PlotDesignation::XErrorMinus));
0343 
0344     action_set_as_yerr = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::YError, false), this);
0345     action_set_as_yerr->setData(static_cast<int>(AbstractColumn::PlotDesignation::YError));
0346 
0347     action_set_as_yerr_plus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::YErrorPlus, false), this);
0348     action_set_as_yerr_plus->setData(static_cast<int>(AbstractColumn::PlotDesignation::YErrorPlus));
0349 
0350     action_set_as_yerr_minus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::YErrorMinus, false), this);
0351     action_set_as_yerr_minus->setData(static_cast<int>(AbstractColumn::PlotDesignation::YErrorMinus));
0352 
0353     // data manipulation
0354     action_add_value = new QAction(i18n("Add"), this);
0355     action_add_value->setData(AddSubtractValueDialog::Add);
0356     action_subtract_value = new QAction(i18n("Subtract"), this);
0357     action_subtract_value->setData(AddSubtractValueDialog::Subtract);
0358     action_multiply_value = new QAction(i18n("Multiply"), this);
0359     action_multiply_value->setData(AddSubtractValueDialog::Multiply);
0360     action_divide_value = new QAction(i18n("Divide"), this);
0361     action_divide_value->setData(AddSubtractValueDialog::Divide);
0362     action_drop_values = new QAction(QIcon::fromTheme(QStringLiteral("delete-table-row")), i18n("Drop Values"), this);
0363     action_mask_values = new QAction(QIcon::fromTheme(QStringLiteral("hide_table_row")), i18n("Mask Values"), this);
0364 
0365     action_reverse_columns = new QAction(QIcon::fromTheme(QStringLiteral("reverse")), i18n("Reverse"), this);
0366     //  action_join_columns = new QAction(QIcon::fromTheme(QString()), i18n("Join"), this);
0367 
0368     // algorithms - baseline subtraction, outliar removal, etc.
0369     action_subtract_baseline = new QAction(i18n("Subtract Baseline"), this);
0370     action_subtract_baseline->setData(AddSubtractValueDialog::SubtractBaseline);
0371 
0372     // normalization
0373     normalizeColumnActionGroup = new QActionGroup(this);
0374     QAction* normalizeAction = new QAction(i18n("Divide by Sum"), normalizeColumnActionGroup);
0375     normalizeAction->setData(DivideBySum);
0376 
0377     normalizeAction = new QAction(i18n("Divide by Min"), normalizeColumnActionGroup);
0378     normalizeAction->setData(DivideByMin);
0379 
0380     normalizeAction = new QAction(i18n("Divide by Max"), normalizeColumnActionGroup);
0381     normalizeAction->setData(DivideByMax);
0382 
0383     normalizeAction = new QAction(i18n("Divide by Count"), normalizeColumnActionGroup);
0384     normalizeAction->setData(DivideByCount);
0385 
0386     normalizeAction = new QAction(i18n("Divide by Mean"), normalizeColumnActionGroup);
0387     normalizeAction->setData(DivideByMean);
0388 
0389     normalizeAction = new QAction(i18n("Divide by Median"), normalizeColumnActionGroup);
0390     normalizeAction->setData(DivideByMedian);
0391 
0392     normalizeAction = new QAction(i18n("Divide by Mode"), normalizeColumnActionGroup);
0393     normalizeAction->setData(DivideByMode);
0394 
0395     normalizeAction = new QAction(i18n("Divide by Range"), normalizeColumnActionGroup);
0396     normalizeAction->setData(DivideByRange);
0397 
0398     normalizeAction = new QAction(i18n("Divide by SD"), normalizeColumnActionGroup);
0399     normalizeAction->setData(DivideBySD);
0400 
0401     normalizeAction = new QAction(i18n("Divide by MAD"), normalizeColumnActionGroup);
0402     normalizeAction->setData(DivideByMAD);
0403 
0404     normalizeAction = new QAction(i18n("Divide by IQR"), normalizeColumnActionGroup);
0405     normalizeAction->setData(DivideByIQR);
0406 
0407     normalizeAction = new QAction(i18n("(x-Mean)/SD"), normalizeColumnActionGroup);
0408     normalizeAction->setData(ZScoreSD);
0409 
0410     normalizeAction = new QAction(i18n("(x-Median)/MAD"), normalizeColumnActionGroup);
0411     normalizeAction->setData(ZScoreMAD);
0412 
0413     normalizeAction = new QAction(i18n("(x-Median)/IQR"), normalizeColumnActionGroup);
0414     normalizeAction->setData(ZScoreIQR);
0415 
0416     normalizeAction = new QAction(i18n("Rescale to [a, b]"), normalizeColumnActionGroup);
0417     normalizeAction->setData(Rescale);
0418 
0419     //  action_normalize_selection = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize Selection"), this);
0420 
0421     // Tukey's ladder of powers
0422     ladderOfPowersActionGroup = new QActionGroup(this);
0423 
0424     QAction* ladderAction = new QAction(QStringLiteral("x³"), ladderOfPowersActionGroup);
0425     ladderAction->setData(Cube);
0426 
0427     ladderAction = new QAction(QStringLiteral("x²"), ladderOfPowersActionGroup);
0428     ladderAction->setData(Squared);
0429 
0430     ladderAction = new QAction(QStringLiteral("√x"), ladderOfPowersActionGroup);
0431     ladderAction->setData(SquareRoot);
0432 
0433     ladderAction = new QAction(QLatin1String("log(x)"), ladderOfPowersActionGroup);
0434     ladderAction->setData(Log);
0435 
0436     ladderAction = new QAction(QStringLiteral("1/√x"), ladderOfPowersActionGroup);
0437     ladderAction->setData(InverseSquareRoot);
0438 
0439     ladderAction = new QAction(QLatin1String("1/x"), ladderOfPowersActionGroup);
0440     ladderAction->setData(Inverse);
0441 
0442     ladderAction = new QAction(QStringLiteral("1/x²"), ladderOfPowersActionGroup);
0443     ladderAction->setData(InverseSquared);
0444 
0445     // sort and statistics
0446     action_sort = new QAction(QIcon::fromTheme(QStringLiteral("view-sort")), i18n("&Sort..."), this);
0447     action_sort->setToolTip(i18n("Sort multiple columns together"));
0448     action_sort_asc = new QAction(QIcon::fromTheme(QStringLiteral("view-sort-ascending")), i18n("Sort &Ascending"), this);
0449     action_sort_asc->setToolTip(i18n("Sort the selected columns separately in ascending order"));
0450     action_sort_desc = new QAction(QIcon::fromTheme(QStringLiteral("view-sort-descending")), i18n("Sort &Descending"), this);
0451     action_sort_desc->setToolTip(i18n("Sort the selected columns separately in descending order"));
0452     action_statistics_columns = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Column Statistics..."), this);
0453 
0454     // conditional formatting
0455     action_formatting_heatmap = new QAction(QIcon::fromTheme(QStringLiteral("color-management")), i18n("Heatmap"), this);
0456     action_formatting_remove = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Delete"), this);
0457 
0458     // row related actions
0459     action_insert_row_above = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-above")), i18n("Insert Row Above"), this);
0460     action_insert_row_below = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Insert Row Below"), this);
0461     // TODO: setting of the following shortcut collides with the key press handling in the event filter
0462     // action_insert_row_below->setShortcut(Qt::Key_Insert);
0463     action_insert_rows_above = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-above")), i18n("Insert Multiple Rows Above"), this);
0464     action_insert_rows_below = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Insert Multiple Rows Below"), this);
0465     action_remove_rows = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Remo&ve Selected Row(s)"), this);
0466     action_remove_missing_value_rows = new QAction(QIcon::fromTheme(QStringLiteral("delete-table-row")), i18n("Delete Rows With Missing Values"), this);
0467     action_mask_missing_value_rows = new QAction(QIcon::fromTheme(QStringLiteral("hide_table_row")), i18n("Mask Rows With Missing Values"), this);
0468     action_statistics_rows = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Row Statisti&cs"), this);
0469 
0470     // Analyze and plot menu actions
0471     addDataReductionAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Reduce Data"), this);
0472     //  addDataReductionAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-data-reduction-curve")), i18n("Reduce Data"), this);
0473     addDataReductionAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::DataReduction));
0474     addDifferentiationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Differentiate"), this);
0475     //  addDifferentiationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-differentiation-curve")), i18n("Differentiate"), this);
0476     addDifferentiationAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Differentiation));
0477     addIntegrationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Integrate"), this);
0478     //  addIntegrationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-integration-curve")), i18n("Integrate"), this);
0479     addIntegrationAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Integration));
0480     addInterpolationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-interpolation-curve")), i18n("Interpolate"), this);
0481     addInterpolationAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Interpolation));
0482     addSmoothAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-smoothing-curve")), i18n("Smooth"), this);
0483     addSmoothAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Smoothing));
0484 
0485     QAction* fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Linear"), this);
0486     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitLinear));
0487     addFitAction.append(fitAction);
0488 
0489     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Power"), this);
0490     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitPower));
0491     addFitAction.append(fitAction);
0492 
0493     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Exponential (degree 1)"), this);
0494     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitExp1));
0495     addFitAction.append(fitAction);
0496 
0497     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Exponential (degree 2)"), this);
0498     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitExp2));
0499     addFitAction.append(fitAction);
0500 
0501     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Inverse Exponential"), this);
0502     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitInvExp));
0503     addFitAction.append(fitAction);
0504 
0505     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Gauss"), this);
0506     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitGauss));
0507     addFitAction.append(fitAction);
0508 
0509     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Cauchy-Lorentz"), this);
0510     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitCauchyLorentz));
0511     addFitAction.append(fitAction);
0512 
0513     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Arc Tangent"), this);
0514     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitTan));
0515     addFitAction.append(fitAction);
0516 
0517     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Hyperbolic Tangent"), this);
0518     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitTanh));
0519     addFitAction.append(fitAction);
0520 
0521     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Error Function"), this);
0522     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitErrFunc));
0523     addFitAction.append(fitAction);
0524 
0525     fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Custom"), this);
0526     fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitCustom));
0527     addFitAction.append(fitAction);
0528 
0529     addFourierFilterAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-filter-curve")), i18n("Fourier Filter"), this);
0530     addFourierFilterAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FourierFilter));
0531 
0532     connectActions();
0533 }
0534 
0535 void SpreadsheetView::initMenus() {
0536     initActions();
0537 
0538     // Selection menu
0539     m_selectionMenu = new QMenu(i18n("Selection"), this);
0540     m_selectionMenu->setIcon(QIcon::fromTheme(QStringLiteral("selection")));
0541     connect(m_selectionMenu, &QMenu::aboutToShow, this, &SpreadsheetView::checkSpreadsheetSelectionMenu);
0542 
0543     if (!m_readOnly) {
0544         //      submenu = new QMenu(i18n("Fi&ll Selection With"), this);
0545         //      submenu->setIcon(QIcon::fromTheme(QStringLiteral("select-rectangle")));
0546         //      submenu->addAction(action_fill_sel_row_numbers);
0547         //      submenu->addAction(action_fill_const);
0548         //      m_selectionMenu->addMenu(submenu);
0549         //      m_selectionMenu->addSeparator();
0550         m_selectionMenu->addAction(action_cut_selection);
0551     }
0552 
0553     m_selectionMenu->addAction(action_copy_selection);
0554 
0555     if (!m_readOnly) {
0556         m_selectionMenu->addAction(action_paste_into_selection);
0557         m_selectionMenu->addAction(action_clear_selection);
0558         m_selectionMenu->addSeparator();
0559         m_selectionMenu->addAction(action_mask_selection);
0560         m_selectionMenu->addAction(action_unmask_selection);
0561         m_selectionMenu->addSeparator();
0562         //      m_selectionMenu->addAction(action_normalize_selection);
0563     }
0564 
0565     // plot data menu
0566     plotDataActionGroup = new QActionGroup(this);
0567     connect(plotDataActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::plotData);
0568     m_plotDataMenu = new QMenu(i18n("Plot Data"), this);
0569     PlotDataDialog::fillMenu(m_plotDataMenu, plotDataActionGroup);
0570 
0571     // conditional formatting
0572     m_formattingMenu = new QMenu(i18n("Conditional Formatting"), this);
0573     m_formattingMenu->addAction(action_formatting_heatmap);
0574     m_formattingMenu->addSeparator();
0575     m_formattingMenu->addAction(action_formatting_remove);
0576 
0577     // Column menu
0578     m_columnMenu = new QMenu(this);
0579     m_columnMenu->addMenu(m_plotDataMenu);
0580 
0581     // Data fit sub-menu
0582     QMenu* dataFitMenu = new QMenu(i18n("Fit"), this);
0583     dataFitMenu->setIcon(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")));
0584     dataFitMenu->addAction(addFitAction.at(0));
0585     dataFitMenu->addAction(addFitAction.at(1));
0586     dataFitMenu->addAction(addFitAction.at(2));
0587     dataFitMenu->addAction(addFitAction.at(3));
0588     dataFitMenu->addAction(addFitAction.at(4));
0589     dataFitMenu->addSeparator();
0590     dataFitMenu->addAction(addFitAction.at(5));
0591     dataFitMenu->addAction(addFitAction.at(6));
0592     dataFitMenu->addSeparator();
0593     dataFitMenu->addAction(addFitAction.at(7));
0594     dataFitMenu->addAction(addFitAction.at(8));
0595     dataFitMenu->addAction(addFitAction.at(9));
0596     dataFitMenu->addSeparator();
0597     dataFitMenu->addAction(addFitAction.at(10));
0598 
0599     // analyze and plot data menu
0600     m_analyzePlotMenu = new QMenu(i18n("Analyze and Plot Data"), this);
0601     m_analyzePlotMenu->addMenu(dataFitMenu);
0602     m_analyzePlotMenu->addSeparator();
0603     m_analyzePlotMenu->addAction(addDifferentiationAction);
0604     m_analyzePlotMenu->addAction(addIntegrationAction);
0605     m_analyzePlotMenu->addSeparator();
0606     m_analyzePlotMenu->addAction(addInterpolationAction);
0607     m_analyzePlotMenu->addAction(addSmoothAction);
0608     m_analyzePlotMenu->addSeparator();
0609     m_analyzePlotMenu->addAction(addFourierFilterAction);
0610     m_analyzePlotMenu->addSeparator();
0611     m_analyzePlotMenu->addAction(addDataReductionAction);
0612     m_columnMenu->addMenu(m_analyzePlotMenu);
0613 
0614     m_columnSetAsMenu = new QMenu(i18n("Set Column As"), this);
0615     m_columnMenu->addSeparator();
0616     m_columnSetAsMenu->addAction(action_set_as_x);
0617     m_columnSetAsMenu->addAction(action_set_as_y);
0618     m_columnSetAsMenu->addAction(action_set_as_z);
0619     m_columnSetAsMenu->addSeparator();
0620     m_columnSetAsMenu->addAction(action_set_as_xerr);
0621     m_columnSetAsMenu->addAction(action_set_as_xerr_plus);
0622     m_columnSetAsMenu->addAction(action_set_as_xerr_minus);
0623     m_columnSetAsMenu->addSeparator();
0624     m_columnSetAsMenu->addAction(action_set_as_yerr);
0625     m_columnSetAsMenu->addAction(action_set_as_yerr_plus);
0626     m_columnSetAsMenu->addAction(action_set_as_yerr_minus);
0627     m_columnSetAsMenu->addSeparator();
0628     m_columnSetAsMenu->addAction(action_set_as_none);
0629     m_columnMenu->addMenu(m_columnSetAsMenu);
0630 
0631     if (!m_readOnly) {
0632         m_columnGenerateDataMenu = new QMenu(i18n("Generate Data"), this);
0633         m_columnGenerateDataMenu->addAction(action_fill_row_numbers);
0634         m_columnGenerateDataMenu->addAction(action_fill_const);
0635         m_columnGenerateDataMenu->addSeparator();
0636         m_columnGenerateDataMenu->addAction(action_fill_equidistant);
0637         m_columnGenerateDataMenu->addAction(action_fill_random_nonuniform);
0638         m_columnGenerateDataMenu->addAction(action_fill_function);
0639         m_columnGenerateDataMenu->addSeparator();
0640         m_columnGenerateDataMenu->addAction(action_sample_values);
0641         m_columnGenerateDataMenu->addAction(action_flatten_columns);
0642 
0643         m_columnMenu->addSeparator();
0644         m_columnMenu->addMenu(m_columnGenerateDataMenu);
0645         m_columnMenu->addSeparator();
0646 
0647         m_columnManipulateDataMenu = new QMenu(i18n("Manipulate Data"), this);
0648         m_columnManipulateDataMenu->addAction(action_add_value);
0649         m_columnManipulateDataMenu->addAction(action_subtract_value);
0650         m_columnManipulateDataMenu->addAction(action_multiply_value);
0651         m_columnManipulateDataMenu->addAction(action_divide_value);
0652         m_columnManipulateDataMenu->addSeparator();
0653         m_columnManipulateDataMenu->addAction(action_subtract_baseline);
0654         m_columnManipulateDataMenu->addSeparator();
0655         m_columnManipulateDataMenu->addAction(action_reverse_columns);
0656         m_columnManipulateDataMenu->addSeparator();
0657         m_columnManipulateDataMenu->addAction(action_drop_values);
0658         m_columnManipulateDataMenu->addAction(action_mask_values);
0659         m_columnManipulateDataMenu->addSeparator();
0660         //  m_columnManipulateDataMenu->addAction(action_join_columns);
0661 
0662         // normalization menu with the following structure
0663         // Divide by Sum
0664         // Divide by Min
0665         // Divide by Max
0666         // Divide by Count
0667         //--------------
0668         // Divide by Mean
0669         // Divide by Median
0670         // Divide by Mode
0671         //---------------
0672         // Divide by Range
0673         // Divide by SD
0674         // Divide by MAD
0675         // Divide by IQR
0676         //--------------
0677         //(x-Mean)/SD
0678         //(x-Median)/MAD
0679         //(x-Median)/IQR
0680         //--------------
0681         // Rescale to [a, b]
0682 
0683         m_columnNormalizeMenu = new QMenu(i18n("Normalize"), this);
0684         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(0));
0685         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(1));
0686         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(2));
0687         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(3));
0688         m_columnNormalizeMenu->addSeparator();
0689         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(4));
0690         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(5));
0691         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(6));
0692         m_columnNormalizeMenu->addSeparator();
0693         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(7));
0694         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(8));
0695         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(9));
0696         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(10));
0697         m_columnNormalizeMenu->addSeparator();
0698         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(11));
0699         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(12));
0700         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(13));
0701         m_columnNormalizeMenu->addSeparator();
0702         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(14));
0703         m_columnManipulateDataMenu->addMenu(m_columnNormalizeMenu);
0704 
0705         //"Ladder of powers" transformation
0706         m_columnLadderOfPowersMenu = new QMenu(i18n("Ladder of Powers"), this);
0707         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(0));
0708         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(1));
0709         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(2));
0710         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(3));
0711         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(4));
0712         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(5));
0713         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(6));
0714 
0715         m_columnManipulateDataMenu->addSeparator();
0716         m_columnManipulateDataMenu->addMenu(m_columnLadderOfPowersMenu);
0717 
0718         m_columnMenu->addMenu(m_columnManipulateDataMenu);
0719         m_columnMenu->addSeparator();
0720 
0721         m_columnMenu->addAction(action_sort);
0722         m_columnMenu->addAction(action_sort_asc);
0723         m_columnMenu->addAction(action_sort_desc);
0724     }
0725 
0726     m_columnMenu->addSeparator();
0727     m_columnMenu->addMenu(m_formattingMenu);
0728     m_columnMenu->addSeparator();
0729     m_columnMenu->addAction(action_freeze_columns);
0730     m_columnMenu->addSeparator();
0731     m_columnMenu->addAction(action_toggle_comments);
0732     m_columnMenu->addSeparator();
0733     m_columnMenu->addAction(action_statistics_columns);
0734 
0735     if (!m_readOnly) {
0736         m_columnMenu->addSeparator();
0737         m_columnMenu->addAction(action_insert_column_left);
0738         m_columnMenu->addAction(action_insert_column_right);
0739         m_columnMenu->addSeparator();
0740         m_columnMenu->addAction(action_insert_columns_left);
0741         m_columnMenu->addAction(action_insert_columns_right);
0742         m_columnMenu->addSeparator();
0743         m_columnMenu->addAction(action_remove_columns);
0744         m_columnMenu->addAction(action_clear_columns);
0745         m_columnMenu->addSeparator();
0746         m_columnMenu->addAction(action_remove_missing_value_rows);
0747         m_columnMenu->addAction(action_mask_missing_value_rows);
0748     }
0749 
0750     // Spreadsheet menu
0751     m_spreadsheetMenu = new QMenu(this);
0752     createContextMenu(m_spreadsheetMenu);
0753 
0754     // Row menu
0755     m_rowMenu = new QMenu(this);
0756     if (!m_readOnly) {
0757         //      submenu = new QMenu(i18n("Fi&ll Selection With"), this);
0758         //      submenu->addAction(action_fill_sel_row_numbers);
0759         //      submenu->addAction(action_fill_const);
0760         //      m_rowMenu->addMenu(submenu);
0761         //      m_rowMenu->addSeparator();
0762 
0763         m_rowMenu->addAction(action_insert_row_above);
0764         m_rowMenu->addAction(action_insert_row_below);
0765         m_rowMenu->addSeparator();
0766 
0767         m_rowMenu->addAction(action_insert_rows_above);
0768         m_rowMenu->addAction(action_insert_rows_below);
0769         m_rowMenu->addSeparator();
0770 
0771         m_rowMenu->addAction(action_remove_rows);
0772         m_rowMenu->addAction(action_clear_selection);
0773         m_rowMenu->addSeparator();
0774 
0775         m_rowMenu->addAction(action_remove_missing_value_rows);
0776         m_rowMenu->addAction(action_mask_missing_value_rows);
0777     }
0778     m_rowMenu->addSeparator();
0779     m_rowMenu->addAction(action_statistics_rows);
0780 }
0781 
0782 void SpreadsheetView::connectActions() {
0783     connect(action_cut_selection, &QAction::triggered, this, &SpreadsheetView::cutSelection);
0784     connect(action_copy_selection, &QAction::triggered, this, &SpreadsheetView::copySelection);
0785     connect(action_paste_into_selection, &QAction::triggered, this, &SpreadsheetView::pasteIntoSelection);
0786     connect(action_mask_selection, &QAction::triggered, this, &SpreadsheetView::maskSelection);
0787     connect(action_unmask_selection, &QAction::triggered, this, &SpreadsheetView::unmaskSelection);
0788 
0789     connect(action_clear_selection, &QAction::triggered, this, &SpreadsheetView::clearSelectedCells);
0790     //  connect(action_recalculate, &QAction::triggered, this, &SpreadsheetView::recalculateSelectedCells);
0791     connect(action_fill_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillWithRowNumbers);
0792     //  connect(action_fill_sel_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRowNumbers);
0793     //  connect(action_fill_random, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRandomNumbers);
0794     connect(action_fill_random_nonuniform, &QAction::triggered, this, &SpreadsheetView::fillWithRandomValues);
0795     connect(action_fill_equidistant, &QAction::triggered, this, &SpreadsheetView::fillWithEquidistantValues);
0796     connect(action_fill_function, &QAction::triggered, this, &SpreadsheetView::fillWithFunctionValues);
0797     connect(action_fill_const, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithConstValues);
0798     connect(action_select_all, &QAction::triggered, m_tableView, &QTableView::selectAll);
0799     connect(action_clear_spreadsheet, &QAction::triggered, m_spreadsheet, QOverload<>::of(&Spreadsheet::clear));
0800     connect(action_clear_masks, &QAction::triggered, m_spreadsheet, &Spreadsheet::clearMasks);
0801     connect(action_go_to_cell, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::goToCell));
0802     connect(action_search, &QAction::triggered, this, &SpreadsheetView::searchReplace);
0803     connect(action_search_replace, &QAction::triggered, this, &SpreadsheetView::searchReplace);
0804 
0805     connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft);
0806     connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight);
0807     connect(action_insert_columns_left, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertColumnsLeft));
0808     connect(action_insert_columns_right, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertColumnsRight));
0809     connect(action_remove_columns, &QAction::triggered, this, &SpreadsheetView::removeSelectedColumns);
0810     connect(action_clear_columns, &QAction::triggered, this, &SpreadsheetView::clearSelectedColumns);
0811     connect(action_freeze_columns, &QAction::triggered, this, &SpreadsheetView::toggleFreezeColumn);
0812     connect(action_set_as_none, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0813     connect(action_set_as_x, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0814     connect(action_set_as_y, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0815     connect(action_set_as_z, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0816     connect(action_set_as_xerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0817     connect(action_set_as_xerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0818     connect(action_set_as_xerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0819     connect(action_set_as_yerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0820     connect(action_set_as_yerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0821     connect(action_set_as_yerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
0822 
0823     // data manipulation
0824     connect(action_add_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
0825     connect(action_subtract_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
0826     connect(action_multiply_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
0827     connect(action_divide_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
0828     connect(action_reverse_columns, &QAction::triggered, this, &SpreadsheetView::reverseColumns);
0829     connect(action_drop_values, &QAction::triggered, this, &SpreadsheetView::dropColumnValues);
0830     connect(action_mask_values, &QAction::triggered, this, &SpreadsheetView::maskColumnValues);
0831     connect(action_sample_values, &QAction::triggered, this, &SpreadsheetView::sampleColumnValues);
0832     connect(action_flatten_columns, &QAction::triggered, this, &SpreadsheetView::flattenColumns);
0833 
0834     // algorithms
0835     connect(action_subtract_baseline, &QAction::triggered, this, &SpreadsheetView::modifyValues);
0836 
0837     //  connect(action_join_columns, &QAction::triggered, this, &SpreadsheetView::joinColumns);
0838     connect(normalizeColumnActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::normalizeSelectedColumns);
0839     connect(ladderOfPowersActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::powerTransformSelectedColumns);
0840     //  connect(action_normalize_selection, &QAction::triggered, this, &SpreadsheetView::normalizeSelection);
0841 
0842     // sort
0843     connect(action_sort, &QAction::triggered, this, &SpreadsheetView::sortCustom);
0844     connect(action_sort_asc, &QAction::triggered, this, &SpreadsheetView::sortAscending);
0845     connect(action_sort_desc, &QAction::triggered, this, &SpreadsheetView::sortDescending);
0846 
0847     // conditional formatting
0848     connect(action_formatting_heatmap, &QAction::triggered, this, &SpreadsheetView::formatHeatmap);
0849     connect(action_formatting_remove, &QAction::triggered, this, &SpreadsheetView::removeFormat);
0850 
0851     // statistics
0852     connect(action_statistics_columns, &QAction::triggered, this, &SpreadsheetView::showColumnStatistics);
0853     connect(action_statistics_all_columns, &QAction::triggered, this, &SpreadsheetView::showAllColumnsStatistics);
0854     connect(action_statistics_spreadsheet, &QAction::toggled, m_spreadsheet, &Spreadsheet::toggleStatisticsSpreadsheet);
0855 
0856     connect(action_insert_row_above, &QAction::triggered, this, &SpreadsheetView::insertRowAbove);
0857     connect(action_insert_row_below, &QAction::triggered, this, &SpreadsheetView::insertRowBelow);
0858     connect(action_insert_rows_above, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertRowsAbove));
0859     connect(action_insert_rows_below, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertRowsBelow));
0860     connect(action_remove_rows, &QAction::triggered, this, &SpreadsheetView::removeSelectedRows);
0861     connect(action_remove_missing_value_rows, &QAction::triggered, m_spreadsheet, &Spreadsheet::removeEmptyRows);
0862     connect(action_mask_missing_value_rows, &QAction::triggered, m_spreadsheet, &Spreadsheet::maskEmptyRows);
0863     connect(action_statistics_rows, &QAction::triggered, this, &SpreadsheetView::showRowStatistics);
0864     connect(action_toggle_comments, &QAction::triggered, this, &SpreadsheetView::toggleComments);
0865 
0866     connect(addDataReductionAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0867     connect(addDifferentiationAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0868     connect(addIntegrationAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0869     connect(addInterpolationAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0870     connect(addSmoothAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0871     for (const auto& action : addFitAction)
0872         connect(action, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0873     connect(addFourierFilterAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData);
0874 }
0875 
0876 void SpreadsheetView::fillToolBar(QToolBar* toolBar) {
0877     if (!m_readOnly) {
0878         toolBar->addAction(action_insert_row_above);
0879         toolBar->addAction(action_insert_row_below);
0880         toolBar->addAction(action_remove_rows);
0881     }
0882     // toolBar->addAction(action_statistics_rows); // TODO: the status enabled or not needs to be synchronized with the current selection (has valid numeric
0883     // values?)
0884     toolBar->addSeparator();
0885     if (!m_readOnly) {
0886         toolBar->addAction(action_insert_column_left);
0887         toolBar->addAction(action_insert_column_right);
0888         toolBar->addAction(action_remove_columns);
0889     }
0890 
0891     // toolBar->addAction(action_statistics_columns);
0892     if (!m_readOnly) {
0893         toolBar->addSeparator();
0894         toolBar->addAction(action_sort);
0895         toolBar->addAction(action_sort_asc);
0896         toolBar->addAction(action_sort_desc);
0897     }
0898 }
0899 
0900 #ifdef HAVE_TOUCHBAR
0901 void SpreadsheetView::fillTouchBar(KDMacTouchBar* touchBar) {
0902     // touchBar->addAction(action_insert_column_right);
0903 }
0904 #endif
0905 
0906 /*!
0907  * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions.
0908  * The menu is used
0909  *   - as the context menu in SpreadsheetView
0910  *   - as the "spreadsheet menu" in the main menu-bar (called form MainWin)
0911  *   - as a part of the spreadsheet context menu in project explorer
0912  */
0913 void SpreadsheetView::createContextMenu(QMenu* menu) {
0914     Q_ASSERT(menu);
0915 
0916     if (!m_selectionMenu)
0917         initMenus();
0918 
0919     checkSpreadsheetMenu();
0920 
0921     QAction* firstAction = nullptr;
0922     // if we're populating the context menu for the project explorer, then
0923     // there're already actions available there. Skip the first title-action
0924     // and insert the action at the beginning of the menu.
0925     if (menu->actions().size() > 1)
0926         firstAction = menu->actions().at(1);
0927 
0928     if (m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0) {
0929         menu->insertMenu(firstAction, m_plotDataMenu);
0930         menu->insertMenu(firstAction, m_analyzePlotMenu);
0931         menu->insertSeparator(firstAction);
0932     }
0933     menu->insertMenu(firstAction, m_selectionMenu);
0934     menu->insertSeparator(firstAction);
0935     menu->insertAction(firstAction, action_select_all);
0936     if (!m_readOnly) {
0937         menu->insertAction(firstAction, action_clear_spreadsheet);
0938         menu->insertAction(firstAction, action_clear_masks);
0939         menu->insertAction(firstAction, action_sort);
0940         menu->insertSeparator(firstAction);
0941     }
0942 
0943     menu->insertMenu(firstAction, m_formattingMenu);
0944     menu->insertSeparator(firstAction);
0945     menu->insertAction(firstAction, action_go_to_cell);
0946     menu->insertAction(firstAction, action_search);
0947     if (!m_readOnly)
0948         menu->insertAction(firstAction, action_search_replace);
0949     menu->insertSeparator(firstAction);
0950     menu->insertAction(firstAction, action_toggle_comments);
0951     menu->insertSeparator(firstAction);
0952     menu->insertAction(firstAction, action_statistics_spreadsheet);
0953     menu->insertSeparator(firstAction);
0954     menu->insertAction(firstAction, action_statistics_all_columns);
0955     menu->insertSeparator(firstAction);
0956 }
0957 
0958 /*!
0959  * adds column specific actions in SpreadsheetView to the context menu shown in the project explorer.
0960  */
0961 void SpreadsheetView::fillColumnContextMenu(QMenu* menu, Column* column) {
0962     if (!column)
0963         return; // should never happen, since the sender is always a Column
0964 
0965     if (!m_selectionMenu)
0966         initMenus();
0967 
0968     const bool numeric = column->isNumeric();
0969     const bool datetime = (column->columnMode() == AbstractColumn::ColumnMode::DateTime);
0970 
0971     QAction* firstAction = menu->actions().at(1);
0972     menu->insertMenu(firstAction, m_plotDataMenu);
0973     menu->insertMenu(firstAction, m_analyzePlotMenu);
0974     menu->insertSeparator(firstAction);
0975 
0976     if (numeric)
0977         menu->insertMenu(firstAction, m_columnSetAsMenu);
0978 
0979     if (!m_readOnly) {
0980         if (numeric) {
0981             menu->insertSeparator(firstAction);
0982             menu->insertMenu(firstAction, m_columnGenerateDataMenu);
0983             menu->insertSeparator(firstAction);
0984         }
0985 
0986         if (numeric || datetime) {
0987             menu->insertMenu(firstAction, m_columnManipulateDataMenu);
0988             menu->insertSeparator(firstAction);
0989         }
0990 
0991         menu->insertAction(firstAction, action_sort);
0992         menu->insertAction(firstAction, action_sort_asc);
0993         menu->insertAction(firstAction, action_sort_desc);
0994     }
0995 
0996     menu->insertSeparator(firstAction);
0997     menu->insertMenu(firstAction, m_formattingMenu);
0998     menu->insertSeparator(firstAction);
0999     menu->insertAction(firstAction, action_freeze_columns);
1000     menu->insertSeparator(firstAction);
1001     menu->insertAction(firstAction, action_toggle_comments);
1002     menu->insertSeparator(firstAction);
1003     menu->insertAction(firstAction, action_statistics_columns);
1004 
1005     checkColumnMenus(QVector<Column*>{column});
1006 }
1007 
1008 // SLOTS
1009 void SpreadsheetView::handleAspectsAdded(int first, int last) {
1010     if (m_suppressResize) {
1011         m_suppressResize = false;
1012         return;
1013     }
1014     PERFTRACE(QLatin1String(Q_FUNC_INFO));
1015 
1016     const auto& children = m_spreadsheet->children<Column>();
1017 
1018     for (int i = first; i <= last; i++) {
1019         const auto* col = children.at(i);
1020         // TODO: this makes it slow!
1021         if (col->width() == 0)
1022             m_tableView->resizeColumnToContents(i);
1023         else
1024             m_tableView->setColumnWidth(i, col->width());
1025     }
1026 
1027     goToCell(0, last);
1028 }
1029 
1030 void SpreadsheetView::handleAspectAboutToBeRemoved(int first, int last) {
1031     const auto& children = m_spreadsheet->children<Column>();
1032     if (first < 0 || first >= children.count() || last >= children.count() || first > last)
1033         return;
1034 
1035     for (int i = first; i <= last; i++)
1036         disconnect(children.at(i), nullptr, this, nullptr);
1037 }
1038 
1039 void SpreadsheetView::handleHorizontalSectionResized(int logicalIndex, int /* oldSize */, int newSize) {
1040     // save the new size in the column
1041     Column* col = m_spreadsheet->child<Column>(logicalIndex);
1042     col->setWidth(newSize);
1043 
1044     if (m_frozenTableView && logicalIndex == 0) {
1045         m_frozenTableView->setColumnWidth(0, newSize);
1046         updateFrozenTableGeometry();
1047     }
1048 }
1049 
1050 void SpreadsheetView::goToCell(int row, int col) {
1051     QModelIndex index = m_model->index(row, col);
1052     m_tableView->scrollTo(index);
1053     m_tableView->setCurrentIndex(index);
1054 }
1055 
1056 void SpreadsheetView::selectCell(int row, int col) {
1057     m_tableView->selectionModel()->select(m_model->index(row, col), QItemSelectionModel::Select);
1058 }
1059 
1060 void SpreadsheetView::clearSelection() {
1061     m_tableView->selectionModel()->clear();
1062 }
1063 
1064 void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) {
1065     static bool inside = false;
1066     if (inside)
1067         return;
1068 
1069     Q_ASSERT(index == from);
1070 
1071     inside = true;
1072     m_tableView->horizontalHeader()->moveSection(to, from);
1073     inside = false;
1074     m_spreadsheet->moveColumn(from, to);
1075 }
1076 
1077 // TODO Implement the "change of the column name"-mode upon a double click
1078 void SpreadsheetView::handleHorizontalHeaderDoubleClicked(int /*index*/) {
1079 }
1080 
1081 /*!
1082   Returns whether comments are shown currently or not
1083 */
1084 bool SpreadsheetView::areCommentsShown() const {
1085     return m_horizontalHeader->areCommentsShown();
1086 }
1087 
1088 /*!
1089   toggles the column comment in the horizontal header
1090 */
1091 void SpreadsheetView::toggleComments() {
1092     showComments(!areCommentsShown());
1093     // TODO
1094     if (areCommentsShown())
1095         action_toggle_comments->setText(i18n("Hide Comments"));
1096     else
1097         action_toggle_comments->setText(i18n("Show Comments"));
1098 }
1099 
1100 //! Shows (\c on=true) or hides (\c on=false) the column comments in the horizontal header
1101 void SpreadsheetView::showComments(bool on) {
1102     m_horizontalHeader->showComments(on);
1103 }
1104 
1105 void SpreadsheetView::handleHeaderDataChanged(Qt::Orientation orientation, int first, int last) {
1106     if (orientation != Qt::Horizontal)
1107         return;
1108 
1109     for (int index = first; index <= last; ++index)
1110         m_tableView->resizeColumnToContents(index);
1111 }
1112 
1113 /*!
1114  * return the selection model of the tree view, private function wrapper to be able
1115  * to unit-test the selection without exposing the whole internal table view.
1116  */
1117 QItemSelectionModel* SpreadsheetView::selectionModel() {
1118     return m_tableView->selectionModel();
1119 }
1120 
1121 /*!
1122   Returns the number of selected columns.
1123   If \c full is \c true, this function only returns the number of fully selected columns.
1124 */
1125 int SpreadsheetView::selectedColumnCount(bool full) const {
1126     int count = 0;
1127     const int cols = m_spreadsheet->columnCount();
1128     for (int i = 0; i < cols; i++)
1129         if (isColumnSelected(i, full))
1130             count++;
1131     return count;
1132 }
1133 
1134 int SpreadsheetView::selectedRowCount(bool full) const {
1135     if (full)
1136         return m_tableView->selectionModel()->selectedRows().count();
1137     const auto& indexes = m_tableView->selectionModel()->selectedIndexes();
1138     QSet<int> set;
1139     for (auto& index : indexes)
1140         set.insert(index.row());
1141 
1142     return set.count();
1143 }
1144 
1145 /*!
1146   Returns the number of (at least partly) selected columns with the plot designation \param pd .
1147  */
1148 int SpreadsheetView::selectedColumnCount(AbstractColumn::PlotDesignation pd) const {
1149     int count = 0;
1150     const int cols = m_spreadsheet->columnCount();
1151     for (int i = 0; i < cols; i++)
1152         if (isColumnSelected(i, false) && (m_spreadsheet->column(i)->plotDesignation() == pd))
1153             count++;
1154 
1155     return count;
1156 }
1157 
1158 /*!
1159   Returns \c true if column \param col is selected, otherwise returns \c false.
1160   If \param full is \c true, this function only returns true if the whole column is selected.
1161 */
1162 bool SpreadsheetView::isColumnSelected(int col, bool full) const {
1163     if (full)
1164         return m_tableView->selectionModel()->isColumnSelected(col);
1165     else
1166         return m_tableView->selectionModel()->columnIntersectsSelection(col);
1167 }
1168 
1169 /*!
1170   Returns all selected columns.
1171   If \param full is true, this function only returns a column if the whole column is selected.
1172   */
1173 QVector<Column*> SpreadsheetView::selectedColumns(bool full) const {
1174     QVector<Column*> columns;
1175     const int cols = m_spreadsheet->columnCount();
1176     for (int i = 0; i < cols; i++)
1177         if (isColumnSelected(i, full))
1178             columns << m_spreadsheet->column(i);
1179 
1180     return columns;
1181 }
1182 
1183 /*!
1184   Returns \c true if row \param row is selected; otherwise returns \c false
1185   If \param full is \c true, this function only returns \c true if the whole row is selected.
1186 */
1187 bool SpreadsheetView::isRowSelected(int row, bool full) const {
1188     if (full)
1189         return m_tableView->selectionModel()->isRowSelected(row);
1190     else
1191         return m_tableView->selectionModel()->rowIntersectsSelection(row);
1192 }
1193 
1194 /*!
1195   Return the index of the first selected column.
1196   If \param full is \c true, this function only looks for fully selected columns.
1197 */
1198 int SpreadsheetView::firstSelectedColumn(bool full) const {
1199     const int cols = m_spreadsheet->columnCount();
1200     for (int i = 0; i < cols; i++) {
1201         if (isColumnSelected(i, full))
1202             return i;
1203     }
1204     return -1;
1205 }
1206 
1207 /*!
1208   Return the index of the last selected column.
1209   If \param full is \c true, this function only looks for fully selected columns.
1210   */
1211 int SpreadsheetView::lastSelectedColumn(bool full) const {
1212     const int cols = m_spreadsheet->columnCount();
1213     for (int i = cols - 1; i >= 0; i--)
1214         if (isColumnSelected(i, full))
1215             return i;
1216 
1217     return -2;
1218 }
1219 
1220 /*!
1221   Return the index of the first selected row.
1222   If \param full is \c true, this function only looks for fully selected rows.
1223   */
1224 int SpreadsheetView::firstSelectedRow(bool full) const {
1225     QModelIndexList indexes;
1226     if (!full)
1227         indexes = m_tableView->selectionModel()->selectedIndexes();
1228     else
1229         indexes = m_tableView->selectionModel()->selectedRows();
1230 
1231     if (!indexes.empty())
1232         return indexes.first().row();
1233     else
1234         return -1;
1235 }
1236 
1237 /*!
1238   Return the index of the last selected row.
1239   If \param full is \c true, this function only looks for fully selected rows.
1240   */
1241 int SpreadsheetView::lastSelectedRow(bool full) const {
1242     QModelIndexList indexes;
1243     if (!full)
1244         indexes = m_tableView->selectionModel()->selectedIndexes();
1245     else
1246         indexes = m_tableView->selectionModel()->selectedRows();
1247 
1248     if (!indexes.empty())
1249         return indexes.last().row();
1250     else
1251         return -2;
1252 }
1253 
1254 /*!
1255   Return whether a cell is selected
1256  */
1257 bool SpreadsheetView::isCellSelected(int row, int col) const {
1258     if (row < 0 || col < 0 || row >= m_spreadsheet->rowCount() || col >= m_spreadsheet->columnCount())
1259         return false;
1260 
1261     return m_tableView->selectionModel()->isSelected(m_model->index(row, col));
1262 }
1263 
1264 /*!
1265   Get the complete set of selected rows.
1266  */
1267 IntervalAttribute<bool> SpreadsheetView::selectedRows(bool full) const {
1268     IntervalAttribute<bool> result;
1269     const int rows = m_spreadsheet->rowCount();
1270     for (int i = 0; i < rows; i++)
1271         if (isRowSelected(i, full))
1272             result.setValue(i, true);
1273     return result;
1274 }
1275 
1276 /*!
1277   Select/Deselect a cell.
1278  */
1279 void SpreadsheetView::setCellSelected(int row, int col, bool select) {
1280     m_tableView->selectionModel()->select(m_model->index(row, col), select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
1281 }
1282 
1283 /*!
1284   Select/Deselect a range of cells.
1285  */
1286 void SpreadsheetView::setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select) {
1287     QModelIndex top_left = m_model->index(first_row, first_col);
1288     QModelIndex bottom_right = m_model->index(last_row, last_col);
1289     m_tableView->selectionModel()->select(QItemSelection(top_left, bottom_right), select ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Deselect);
1290 }
1291 
1292 /*!
1293   Determine the current cell (-1 if no cell is designated as the current).
1294  */
1295 void SpreadsheetView::getCurrentCell(int* row, int* col) const {
1296     QModelIndex index = m_tableView->selectionModel()->currentIndex();
1297     if (index.isValid()) {
1298         *row = index.row();
1299         *col = index.column();
1300     } else {
1301         *row = -1;
1302         *col = -1;
1303     }
1304 }
1305 
1306 bool SpreadsheetView::eventFilter(QObject* watched, QEvent* event) {
1307     if (event->type() == QEvent::ContextMenu) {
1308         // initialize all menus and actions if not done yet
1309         if (!m_plotDataMenu)
1310             initMenus();
1311 
1312         auto* cm_event = static_cast<QContextMenuEvent*>(event);
1313         const QPoint global_pos = cm_event->globalPos();
1314 
1315         if (watched == m_tableView->verticalHeader()) {
1316             bool numeric = true;
1317             const auto& columns = m_spreadsheet->children<Column>();
1318             for (const auto* col : columns) {
1319                 if (!col->isNumeric()) {
1320                     numeric = false;
1321                     break;
1322                 }
1323             }
1324 
1325             int hasValues = 0;
1326             if (numeric) {
1327                 const auto& rows = m_tableView->selectionModel()->selectedRows();
1328                 for (int i = 0; i < rows.count(); ++i) {
1329                     int row = rows.at(i).row();
1330 
1331                     for (int j = 0; j < m_spreadsheet->columnCount(); ++j) {
1332                         hasValues += !std::isnan(m_spreadsheet->column(j)->valueAt(row));
1333                         if (hasValues > 1)
1334                             break;
1335                     }
1336 
1337                     if (hasValues > 1)
1338                         break;
1339                 }
1340             }
1341 
1342             action_statistics_rows->setEnabled(hasValues > 1);
1343             m_rowMenu->exec(global_pos);
1344         } else if ((watched == m_horizontalHeader) || (m_frozenTableView && watched == m_frozenTableView->horizontalHeader()) || !selectedColumns().isEmpty()) {
1345             // if the horizontal header was clicked, select the column under the cursor if not selected yet
1346             if (watched == m_horizontalHeader) {
1347                 const int col = m_horizontalHeader->logicalIndexAt(cm_event->pos());
1348                 if (!isColumnSelected(col, true)) {
1349                     auto* sel_model = m_tableView->selectionModel();
1350                     sel_model->clearSelection();
1351                     QItemSelection selection(m_model->index(0, col, QModelIndex()), m_model->index(m_model->rowCount() - 1, col, QModelIndex()));
1352                     sel_model->select(selection, QItemSelectionModel::Select);
1353                 }
1354             }
1355 
1356             checkColumnMenus(selectedColumns());
1357             m_columnMenu->exec(global_pos);
1358         } else if (watched == this) {
1359             // the cursor position is in one of the cells and no full columns are selected,
1360             // show the global spreadsheet context menu in this case
1361             checkSpreadsheetMenu();
1362             m_spreadsheetMenu->exec(global_pos);
1363         }
1364 
1365         return true;
1366     } else if (event->type() == QEvent::KeyPress) {
1367         auto* key_event = static_cast<QKeyEvent*>(event);
1368         if (key_event->matches(QKeySequence::Copy))
1369             copySelection();
1370         else if (key_event->matches(QKeySequence::Paste)) {
1371             if (!m_readOnly)
1372                 pasteIntoSelection();
1373         } else if (key_event->key() == Qt::Key_Backspace || key_event->matches(QKeySequence::Delete))
1374             clearSelectedCells();
1375         else if (key_event->key() == Qt::Key_Return || key_event->key() == Qt::Key_Enter) {
1376             // only advance for return pressed events in the table,
1377             // ignore it in the search field that has its own handling for it
1378             if (watched == m_tableView)
1379                 advanceCell();
1380         } else if (key_event->key() == Qt::Key_Insert) {
1381             if (!m_editorEntered) {
1382                 if (lastSelectedColumn(true) >= 0)
1383                     insertColumnRight();
1384                 else
1385                     insertRowBelow();
1386             }
1387         } else if (key_event->key() == Qt::Key_Left) {
1388             // adjusted example from
1389             // https://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html
1390             //          QModelIndex current = m_tableView->currentIndex();
1391             //          if (current.column() > 0) {
1392             //              const QRect& rect = m_tableView->visualRect(current);
1393             //              auto* scrollBar = m_tableView->horizontalScrollBar();
1394             //              if (rect.topLeft().x() < m_frozenTableView->columnWidth(0)) {
1395             //                  const int newValue = scrollBar->value() + rect.topLeft().x() - m_frozenTableView->columnWidth(0);
1396             //                  scrollBar->setValue(newValue);
1397             //              }
1398             //          }
1399         } else if (key_event->matches(QKeySequence::Find))
1400             showSearchReplace(/* replace */ false);
1401         else if (key_event->matches(QKeySequence::Replace))
1402             showSearchReplace(/* replace */ true);
1403         else if (key_event->key() == Qt::Key_Escape && m_searchReplaceWidget && m_searchReplaceWidget->isVisible())
1404             m_searchReplaceWidget->hide();
1405         else if (key_event->matches(QKeySequence::Cut))
1406             cutSelection();
1407     }
1408 
1409     return QWidget::eventFilter(watched, event);
1410 }
1411 
1412 /*!
1413     Advance current cell after [Return] or [Enter] was pressed
1414 */
1415 void SpreadsheetView::advanceCell() {
1416     const QModelIndex& idx = m_tableView->currentIndex();
1417     const int row = idx.row();
1418     const int col = idx.column();
1419     if (row + 1 == m_spreadsheet->rowCount())
1420         m_spreadsheet->setRowCount(m_spreadsheet->rowCount() + 1);
1421 
1422     m_tableView->setCurrentIndex(idx.sibling(row + 1, col));
1423 }
1424 
1425 /*!
1426  * disables cell data relevant actions in the spreadsheet menu if there're no cells available
1427  */
1428 void SpreadsheetView::checkSpreadsheetMenu() {
1429     if (!m_plotDataMenu)
1430         initMenus();
1431 
1432     const auto& columns = m_spreadsheet->children<Column>();
1433 
1434     const bool cellsAvail = m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0;
1435     bool hasValues = this->hasValues(columns);
1436     m_plotDataMenu->setEnabled(hasValues);
1437     m_selectionMenu->setEnabled(hasValues);
1438     action_select_all->setEnabled(hasValues);
1439     action_clear_spreadsheet->setEnabled(hasValues);
1440     action_statistics_all_columns->setEnabled(hasValues);
1441     action_go_to_cell->setEnabled(cellsAvail);
1442 
1443     // deactivate the "Clear masks" action if there are no masked cells
1444     bool hasMasked = false;
1445     for (auto* column : columns) {
1446         if (column->maskedIntervals().size() > 0) {
1447             hasMasked = true;
1448             break;
1449         }
1450     }
1451 
1452     action_clear_masks->setVisible(hasMasked);
1453 
1454     // deactivate the "Remove format" action if no columns are formatted
1455     bool hasFormat = false;
1456     for (auto* column : columns) {
1457         if (column->hasHeatmapFormat()) {
1458             hasFormat = true;
1459             break;
1460         }
1461     }
1462 
1463     action_formatting_remove->setVisible(hasFormat);
1464 }
1465 
1466 void SpreadsheetView::checkSpreadsheetSelectionMenu() {
1467     // deactivate mask/unmask actions for the selection
1468     // if there are no unmasked/masked cells in the current selection
1469     const QModelIndexList& indexes = m_tableView->selectionModel()->selectedIndexes();
1470     bool hasMasked = false;
1471     bool hasUnmasked = false;
1472     for (auto& index : qAsConst(indexes)) {
1473         int row = index.row();
1474         int col = index.column();
1475         const auto* column = m_spreadsheet->column(col);
1476         // TODO: the null pointer check shouldn't be actually required here
1477         // but when deleting the columns the selection model in the view
1478         // and the aspect model sometimes get out of sync and we crash...
1479         if (!column)
1480             break;
1481 
1482         if (!hasMasked && column->isMasked(row))
1483             hasMasked = true;
1484 
1485         if (!hasUnmasked && !column->isMasked(row))
1486             hasUnmasked = true;
1487 
1488         if (hasMasked && hasUnmasked)
1489             break;
1490     }
1491 
1492     action_mask_selection->setEnabled(hasUnmasked);
1493     action_unmask_selection->setEnabled(hasMasked);
1494 }
1495 
1496 void SpreadsheetView::checkColumnMenus(const QVector<Column*>& columns) {
1497     bool numeric = true;
1498     bool plottable = true;
1499     bool datetime = false;
1500     bool text = false;
1501     bool hasValues = false;
1502     bool hasFormat = false;
1503     bool hasEnoughValues = false; // enough for statistics (> 1)
1504 
1505     for (const auto* col : columns) {
1506         if (!col->isNumeric()) {
1507             datetime = (col->columnMode() == AbstractColumn::ColumnMode::DateTime);
1508             if (!datetime)
1509                 plottable = false;
1510 
1511             numeric = false;
1512             break;
1513         }
1514     }
1515 
1516     for (const auto* col : columns) {
1517         if (col->columnMode() == AbstractColumn::ColumnMode::Text) {
1518             text = true;
1519             break;
1520         }
1521     }
1522 
1523     for (const auto* col : columns) {
1524         if (col->hasValues()) {
1525             hasValues = true;
1526             break;
1527         }
1528     }
1529 
1530     for (const auto* col : columns) {
1531         if (col->availableRowCount() > 1) {
1532             hasEnoughValues = true;
1533             break;
1534         }
1535     }
1536 
1537     for (const auto* col : columns) {
1538         if (col->hasHeatmapFormat()) {
1539             hasFormat = true;
1540             break;
1541         }
1542     }
1543 
1544     if (isColumnSelected(0, true)) {
1545         action_freeze_columns->setVisible(true);
1546         if (m_frozenTableView) {
1547             if (!m_frozenTableView->isVisible()) {
1548                 action_freeze_columns->setText(i18n("Freeze Column"));
1549                 action_insert_column_left->setEnabled(true);
1550                 action_insert_columns_left->setEnabled(true);
1551             } else {
1552                 action_freeze_columns->setText(i18n("Unfreeze Column"));
1553                 action_insert_column_left->setEnabled(false);
1554                 action_insert_columns_left->setEnabled(false);
1555             }
1556         }
1557     } else
1558         action_freeze_columns->setVisible(false);
1559 
1560     m_plotDataMenu->setEnabled(plottable && hasValues);
1561     m_analyzePlotMenu->setEnabled(numeric && hasValues);
1562     m_columnSetAsMenu->setEnabled(numeric);
1563     action_statistics_columns->setEnabled(hasEnoughValues);
1564     action_clear_columns->setEnabled(hasValues);
1565     m_formattingMenu->setEnabled(hasValues);
1566     action_formatting_remove->setVisible(hasFormat);
1567 
1568     if (m_readOnly)
1569         return;
1570 
1571     // generate data is only possible for numeric columns and if there are cells available
1572     const bool hasCells = m_spreadsheet->rowCount() > 0;
1573     m_columnGenerateDataMenu->setEnabled(hasCells);
1574     action_fill_row_numbers->setEnabled(numeric);
1575     action_fill_const->setEnabled(numeric);
1576     action_fill_equidistant->setEnabled(numeric || datetime);
1577     action_fill_random_nonuniform->setEnabled(numeric);
1578     action_fill_function->setEnabled(numeric);
1579     action_sample_values->setEnabled(hasValues);
1580     action_flatten_columns->setEnabled(hasValues);
1581 
1582     // manipulate data is only possible for numeric and datetime and if there values.
1583     // datetime has only "add/subtract value", everything else is deactivated
1584     m_columnManipulateDataMenu->setEnabled((numeric || datetime || text) && hasValues);
1585     action_add_value->setEnabled(numeric || datetime);
1586     action_subtract_value->setEnabled(numeric || datetime);
1587     action_subtract_baseline->setEnabled(numeric);
1588     action_multiply_value->setEnabled(numeric);
1589     action_divide_value->setEnabled(numeric);
1590     action_reverse_columns->setEnabled(numeric);
1591     action_drop_values->setEnabled(numeric || text || datetime);
1592     action_mask_values->setEnabled(numeric || text || datetime);
1593     m_columnNormalizeMenu->setEnabled(numeric);
1594     m_columnLadderOfPowersMenu->setEnabled(numeric);
1595 }
1596 
1597 bool SpreadsheetView::formulaModeActive() const {
1598     return m_model->formulaModeActive();
1599 }
1600 
1601 void SpreadsheetView::activateFormulaMode(bool on) {
1602     m_model->activateFormulaMode(on);
1603 }
1604 
1605 void SpreadsheetView::goToNextColumn() {
1606     if (m_spreadsheet->columnCount() == 0)
1607         return;
1608 
1609     QModelIndex idx = m_tableView->currentIndex();
1610     int col = idx.column() + 1;
1611     if (col >= m_spreadsheet->columnCount())
1612         col = 0;
1613 
1614     m_tableView->setCurrentIndex(idx.sibling(idx.row(), col));
1615 }
1616 
1617 void SpreadsheetView::goToPreviousColumn() {
1618     if (m_spreadsheet->columnCount() == 0)
1619         return;
1620 
1621     QModelIndex idx = m_tableView->currentIndex();
1622     int col = idx.column() - 1;
1623     if (col < 0)
1624         col = m_spreadsheet->columnCount() - 1;
1625 
1626     m_tableView->setCurrentIndex(idx.sibling(idx.row(), col));
1627 }
1628 
1629 void SpreadsheetView::cutSelection() {
1630     if (firstSelectedRow() < 0)
1631         return;
1632 
1633     WAIT_CURSOR;
1634     m_spreadsheet->beginMacro(i18n("%1: cut selected cells", m_spreadsheet->name()));
1635     copySelection();
1636     clearSelectedCells();
1637     m_spreadsheet->endMacro();
1638     RESET_CURSOR;
1639 }
1640 
1641 void SpreadsheetView::copySelection() {
1642     PERFTRACE(QStringLiteral("copy selected cells"));
1643     const int first_col = firstSelectedColumn();
1644     if (first_col == -1)
1645         return;
1646     const int last_col = lastSelectedColumn();
1647     if (last_col == -2)
1648         return;
1649     const int first_row = firstSelectedRow();
1650     if (first_row == -1)
1651         return;
1652     const int last_row = lastSelectedRow();
1653     if (last_row == -2)
1654         return;
1655     const int cols = last_col - first_col + 1;
1656     const int rows = last_row - first_row + 1;
1657 
1658     WAIT_CURSOR;
1659     QString output_str;
1660 
1661     QVector<Column*> columns;
1662     QVector<char> formats;
1663     for (int c = 0; c < cols; c++) {
1664         Column* col = m_spreadsheet->column(first_col + c);
1665         columns << col;
1666         const auto* outFilter = static_cast<Double2StringFilter*>(col->outputFilter());
1667         formats << outFilter->numericFormat();
1668     }
1669 
1670     const auto numberLocale = QLocale();
1671     for (int r = 0; r < rows; r++) {
1672         for (int c = 0; c < cols; c++) {
1673             const Column* col_ptr = columns.at(c);
1674             if (isCellSelected(first_row + r, first_col + c)) {
1675                 //              if (formulaModeActive())
1676                 //                  output_str += col_ptr->formula(first_row + r);
1677                 //              else
1678                 if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Double)
1679                     output_str += numberLocale.toString(col_ptr->valueAt(first_row + r), formats.at(c), 16); // copy with max. precision
1680                 else if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Integer || col_ptr->columnMode() == AbstractColumn::ColumnMode::BigInt)
1681                     output_str += numberLocale.toString(col_ptr->valueAt(first_row + r));
1682                 else
1683                     output_str += col_ptr->asStringColumn()->textAt(first_row + r);
1684             }
1685             if (c < cols - 1)
1686                 output_str += QLatin1Char('\t');
1687         }
1688         if (r < rows - 1)
1689             output_str += QLatin1Char('\n');
1690     }
1691 
1692     QApplication::clipboard()->setText(output_str);
1693     RESET_CURSOR;
1694 }
1695 /*
1696 bool determineLocale(const QString& value, QLocale& locale) {
1697     int pointIndex = value.indexOf(QLatin1Char('.'));
1698     int commaIndex = value.indexOf(QLatin1Char('.'));
1699     if (pointIndex != -1 && commaIndex != -1) {
1700 
1701     }
1702     return false;
1703 }*/
1704 
1705 void SpreadsheetView::pasteIntoSelection() {
1706     if (m_spreadsheet->columnCount() < 1 || m_spreadsheet->rowCount() < 1)
1707         return;
1708 
1709     const QMimeData* mime_data = QApplication::clipboard()->mimeData();
1710     if (!mime_data->hasFormat(QStringLiteral("text/plain")))
1711         return;
1712 
1713     PERFTRACE(QStringLiteral("paste selected cells"));
1714     WAIT_CURSOR;
1715     m_spreadsheet->beginMacro(i18n("%1: paste from clipboard", m_spreadsheet->name()));
1716 
1717     int first_col = firstSelectedColumn();
1718     int last_col = lastSelectedColumn();
1719     int first_row = firstSelectedRow();
1720     int last_row = lastSelectedRow();
1721     int input_row_count = 0;
1722     int input_col_count = 0;
1723     QVector<QStringList> cellTexts;
1724 
1725     try {
1726         QString input_str = QString::fromUtf8(mime_data->data(QStringLiteral("text/plain"))).trimmed();
1727         QString separator;
1728         if (input_str.indexOf(QLatin1String("\r\n")) != -1)
1729             separator = QLatin1String("\r\n");
1730         else
1731             separator = QLatin1Char('\n');
1732 
1733         QStringList input_rows(input_str.split(separator));
1734         input_str.clear(); // not needed anymore, release memory
1735         input_row_count = input_rows.count();
1736         input_col_count = 0;
1737         bool hasTabs = false;
1738         if (input_row_count > 0 && input_rows.constFirst().indexOf(QLatin1Char('\t')) != -1)
1739             hasTabs = true;
1740 
1741         const auto numberLocale = QLocale();
1742         // TEST ' ' as group separator:
1743         // numberLocale = QLocale(QLocale::French, QLocale::France);
1744         const KConfigGroup group = Settings::group(QStringLiteral("Settings_General"));
1745         for (int i = 0; i < input_row_count; i++) {
1746             if (hasTabs)
1747                 cellTexts.append(input_rows.at(i).split(QLatin1Char('\t')));
1748 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1749             else if (numberLocale.groupSeparator().trimmed().isEmpty()
1750 #else
1751             else if (numberLocale.groupSeparator().isSpace()
1752 #endif
1753                      && !(numberLocale.numberOptions() & QLocale::OmitGroupSeparator)) // locale with ' ' as group separator && omit group separator not set
1754 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
1755                 cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s\\s")), (Qt::SplitBehavior)0x1)); // split with two spaces
1756 #else
1757                 cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s\\s")), (QString::SplitBehavior)0x1)); // split with two spaces
1758 #endif
1759             else
1760                 cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s+"))));
1761 
1762             if (cellTexts.at(i).count() > input_col_count)
1763                 input_col_count = cellTexts.at(i).count();
1764         }
1765 
1766         input_rows.clear(); // not needed anymore, release memory
1767 
1768         // when pasting DateTime data in the format 'yyyy-MM-dd hh:mm:ss' and similar, it get's split above because of the space separator.
1769         // here we check whether we have such a situation and merge the first two columns to get the proper value,
1770         // for example "2018-03-21 10:00:00" and not "2018-03-21" and "10:00:00"
1771         if (!cellTexts.isEmpty() && cellTexts.constFirst().size() > 1) {
1772             const auto& firstCell = cellTexts.constFirst().at(0);
1773             const auto& secondCell = cellTexts.constFirst().at(1);
1774             QString dateTimeFormat; // empty string, we'll auto-detect the format of the data
1775             const auto firstMode = AbstractFileFilter::columnMode(firstCell, dateTimeFormat, numberLocale);
1776             dateTimeFormat.clear();
1777             const auto secondMode = AbstractFileFilter::columnMode(secondCell, dateTimeFormat, numberLocale);
1778 
1779             // if both first columns are DateTime, check whether the combination of them is also DateTime
1780             if (firstMode == AbstractColumn::ColumnMode::DateTime && secondMode == AbstractColumn::ColumnMode::DateTime) {
1781                 dateTimeFormat.clear();
1782                 const QString newCell = firstCell + QLatin1Char(' ') + secondCell;
1783                 const auto newMode = AbstractFileFilter::columnMode(newCell, dateTimeFormat, numberLocale);
1784                 if (newMode == AbstractColumn::ColumnMode::DateTime) {
1785                     // merge the first two colums
1786                     for (auto& row : cellTexts) {
1787                         row[1] = row.at(0) + QLatin1Char(' ') + row.at(1);
1788                         row.takeFirst();
1789                     }
1790                     --input_col_count;
1791                 }
1792             }
1793         }
1794 
1795         //  bool localeDetermined = false;
1796 
1797         // expand the current selection to the needed size if
1798         // 1. there is no selection
1799         // 2. only one cell selected
1800         // 3. the whole column is selected (the use clicked on the header)
1801         // Also, set the proper column mode if the target column doesn't have any values yet
1802         // and set the proper column mode if the column is empty
1803         if ((first_col == -1 || first_row == -1) || (last_row == first_row && last_col == first_col)
1804             || (first_row == 0 && last_row == m_spreadsheet->rowCount() - 1)) {
1805             int current_row, current_col;
1806             getCurrentCell(&current_row, &current_col);
1807             if (current_row == -1)
1808                 current_row = 0;
1809             if (current_col == -1)
1810                 current_col = 0;
1811             setCellSelected(current_row, current_col);
1812             first_col = current_col;
1813             first_row = current_row;
1814             last_row = first_row + input_row_count - 1;
1815             last_col = first_col + input_col_count - 1;
1816             const int columnCount = m_spreadsheet->columnCount();
1817             // if the target columns that are already available don't have any values yet,
1818             // convert their mode to the mode of the data to be pasted
1819             for (int c = first_col; c <= last_col && c < columnCount; ++c) {
1820                 Column* col = m_spreadsheet->column(c);
1821                 if (col->hasValues())
1822                     continue;
1823 
1824                 // first non-empty value in the column to paste determines the column mode/type of the new column to be added
1825                 const int curCol = c - first_col;
1826                 QString nonEmptyValue;
1827                 for (auto& r : cellTexts) {
1828                     if (curCol < r.count() && !r.at(curCol).isEmpty()) {
1829                         nonEmptyValue = r.at(curCol);
1830                         break;
1831                     }
1832                 }
1833 
1834                 //          if (!localeDetermined)
1835                 //              localeDetermined = determineLocale(nonEmptyValue, locale);
1836 
1837                 QString dateTimeFormat; // empty string, we'll auto-detect the format of the data
1838                 const auto mode = AbstractFileFilter::columnMode(nonEmptyValue, dateTimeFormat, numberLocale);
1839                 col->setColumnMode(mode);
1840                 if (mode == AbstractColumn::ColumnMode::DateTime) {
1841                     auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter());
1842                     filter->setFormat(dateTimeFormat);
1843                 }
1844             }
1845 
1846             // add columns if necessary
1847             if (last_col >= columnCount) {
1848                 for (int c = 0; c < last_col - (columnCount - 1); ++c) {
1849                     const int curCol = columnCount - first_col + c;
1850                     // first non-empty value in the column to paste determines the column mode/type of the new column to be added
1851                     QString nonEmptyValue;
1852                     for (auto& r : cellTexts) {
1853                         if (curCol < r.count() && !r.at(curCol).isEmpty()) {
1854                             nonEmptyValue = r.at(curCol);
1855                             break;
1856                         }
1857                     }
1858 
1859                     //              if (!localeDetermined)
1860                     //                  localeDetermined = determineLocale(nonEmptyValue, locale);
1861 
1862                     QString dateTimeFormat; // empty string, we'll auto-detect the format of the data
1863                     const auto mode = AbstractFileFilter::columnMode(nonEmptyValue, dateTimeFormat, numberLocale);
1864                     Column* new_col = new Column(QString::number(curCol), mode);
1865                     if (mode == AbstractColumn::ColumnMode::DateTime) {
1866                         auto* filter = static_cast<DateTime2StringFilter*>(new_col->outputFilter());
1867                         filter->setFormat(dateTimeFormat);
1868                     }
1869                     new_col->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
1870                     new_col->insertRows(0, m_spreadsheet->rowCount());
1871                     m_spreadsheet->addChild(new_col);
1872                 }
1873             }
1874 
1875             // add rows if necessary
1876             if (last_row >= m_spreadsheet->rowCount())
1877                 m_spreadsheet->appendRows(last_row + 1 - m_spreadsheet->rowCount());
1878 
1879             // select the rectangle to be pasted in
1880             setCellsSelected(first_row, first_col, last_row, last_col);
1881         }
1882 
1883         const int rows = last_row - first_row + 1;
1884         const int cols = last_col - first_col + 1;
1885         for (int c = 0; c < cols && c < input_col_count; c++) {
1886             Column* col = m_spreadsheet->column(first_col + c);
1887             col->setSuppressDataChangedSignal(true);
1888             if (col->columnMode() == AbstractColumn::ColumnMode::Double) {
1889                 if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) {
1890                     QVector<double> new_data(rows);
1891                     for (int r = 0; r < rows; ++r) {
1892                         if (c < cellTexts.at(r).count())
1893                             new_data[r] = numberLocale.toDouble(cellTexts.at(r).at(c));
1894                     }
1895                     col->setValues(new_data);
1896                 } else {
1897                     for (int r = 0; r < rows && r < input_row_count; r++) {
1898                         if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) {
1899                             if (!cellTexts.at(r).at(c).isEmpty())
1900                                 col->setValueAt(first_row + r, numberLocale.toDouble(cellTexts.at(r).at(c)));
1901                             else
1902                                 col->setValueAt(first_row + r, std::numeric_limits<double>::quiet_NaN());
1903                         }
1904                     }
1905                 }
1906             } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) {
1907                 if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) {
1908                     QVector<int> new_data(rows);
1909                     for (int r = 0; r < rows; ++r) {
1910                         if (c < cellTexts.at(r).count())
1911                             new_data[r] = numberLocale.toInt(cellTexts.at(r).at(c));
1912                     }
1913                     col->setIntegers(new_data);
1914                 } else {
1915                     for (int r = 0; r < rows && r < input_row_count; r++) {
1916                         if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) {
1917                             if (!cellTexts.at(r).at(c).isEmpty())
1918                                 col->setIntegerAt(first_row + r, numberLocale.toInt(cellTexts.at(r).at(c)));
1919                             else
1920                                 col->setIntegerAt(first_row + r, 0);
1921                         }
1922                     }
1923                 }
1924             } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) {
1925                 if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) {
1926                     QVector<qint64> new_data(rows);
1927                     for (int r = 0; r < rows; ++r)
1928                         new_data[r] = numberLocale.toLongLong(cellTexts.at(r).at(c));
1929                     col->setBigInts(new_data);
1930                 } else {
1931                     for (int r = 0; r < rows && r < input_row_count; r++) {
1932                         if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) {
1933                             if (!cellTexts.at(r).at(c).isEmpty())
1934                                 col->setBigIntAt(first_row + r, numberLocale.toLongLong(cellTexts.at(r).at(c)));
1935                             else
1936                                 col->setBigIntAt(first_row + r, 0);
1937                         }
1938                     }
1939                 }
1940             } else {
1941                 for (int r = 0; r < rows && r < input_row_count; r++) {
1942                     if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) {
1943                         //                  if (formulaModeActive())
1944                         //                      col->setFormula(first_row + r, cellTexts.at(r).at(c));
1945                         //                  else
1946                         col->asStringColumn()->setTextAt(first_row + r, cellTexts.at(r).at(c));
1947                     }
1948                 }
1949             }
1950 
1951             col->setSuppressDataChangedSignal(false);
1952             col->setChanged();
1953         } // end of for-loop
1954     } catch (std::bad_alloc&) {
1955         cellTexts.clear();
1956         m_spreadsheet->endMacro();
1957         RESET_CURSOR;
1958         KMessageBox::error(this, i18n("Not enough memory to finalize this operation."));
1959     }
1960 
1961     m_spreadsheet->endMacro();
1962     RESET_CURSOR;
1963 }
1964 
1965 void SpreadsheetView::maskSelection() {
1966     int first = firstSelectedRow();
1967     if (first < 0)
1968         return;
1969     int last = lastSelectedRow();
1970 
1971     WAIT_CURSOR;
1972     m_spreadsheet->beginMacro(i18n("%1: mask selected cells", m_spreadsheet->name()));
1973 
1974     const auto& columns = selectedColumns(false);
1975     QVector<CartesianPlot*> plots;
1976 
1977     // determine the dependent plots
1978     for (auto* column : columns)
1979         column->addUsedInPlots(plots);
1980 
1981     // suppress retransform in the dependent plots
1982     for (auto* plot : plots)
1983         plot->setSuppressRetransform(true);
1984 
1985     // mask the selected cells
1986     for (auto* column : columns) {
1987         int col = m_spreadsheet->indexOfChild<Column>(column);
1988         for (int row = first; row <= last; row++)
1989             if (isCellSelected(row, col))
1990                 column->setMasked(row);
1991     }
1992 
1993     // retransform the dependent plots
1994     for (auto* plot : plots) {
1995         plot->setSuppressRetransform(false);
1996         // TODO: check which ranges must be updated
1997         plot->dataChanged(-1, -1);
1998     }
1999 
2000     // some cells were masked, enable the "clear masks" action
2001     action_clear_masks->setEnabled(true);
2002 
2003     m_spreadsheet->endMacro();
2004     RESET_CURSOR;
2005 }
2006 
2007 void SpreadsheetView::unmaskSelection() {
2008     int first = firstSelectedRow();
2009     if (first < 0)
2010         return;
2011     int last = lastSelectedRow();
2012 
2013     WAIT_CURSOR;
2014     m_spreadsheet->beginMacro(i18n("%1: unmask selected cells", m_spreadsheet->name()));
2015 
2016     const auto& columns = selectedColumns(false);
2017     QVector<CartesianPlot*> plots;
2018 
2019     // determine the dependent plots
2020     for (auto* column : columns)
2021         column->addUsedInPlots(plots);
2022 
2023     // suppress retransform in the dependent plots
2024     for (auto* plot : plots)
2025         plot->setSuppressRetransform(true);
2026 
2027     // unmask the selected cells
2028     for (auto* column : columns) {
2029         int col = m_spreadsheet->indexOfChild<Column>(column);
2030         for (int row = first; row <= last; row++)
2031             if (isCellSelected(row, col))
2032                 column->setMasked(row, false);
2033     }
2034 
2035     // retransform the dependent plots
2036     for (auto* plot : plots) {
2037         plot->setSuppressRetransform(false);
2038         // TODO: check which ranges must be updated
2039         plot->dataChanged(-1, -1);
2040     }
2041 
2042     m_spreadsheet->endMacro();
2043     RESET_CURSOR;
2044 }
2045 
2046 void SpreadsheetView::plotData(QAction* action) {
2047     auto type = static_cast<PlotDataDialog::PlotType>(action->data().toInt());
2048     auto* dlg = new PlotDataDialog(m_spreadsheet, type);
2049 
2050     // use all spreadsheet columns if no columns are selected
2051     auto columns = selectedColumns(true);
2052     if (columns.isEmpty())
2053         columns = m_spreadsheet->children<Column>();
2054 
2055     dlg->setSelectedColumns(columns);
2056     dlg->exec();
2057 }
2058 
2059 void SpreadsheetView::plotAnalysisData() {
2060     const auto* action = dynamic_cast<const QAction*>(QObject::sender());
2061     auto* dlg = new PlotDataDialog(m_spreadsheet, PlotDataDialog::PlotType::XYCurve);
2062 
2063     auto type = static_cast<XYAnalysisCurve::AnalysisAction>(action->data().toInt());
2064     dlg->setAnalysisAction(type);
2065 
2066     // use all spreadsheet columns if no columns are selected
2067     auto columns = selectedColumns(true);
2068     if (columns.isEmpty())
2069         columns = m_spreadsheet->children<Column>();
2070     dlg->setSelectedColumns(columns);
2071 
2072     dlg->exec();
2073 }
2074 
2075 void SpreadsheetView::fillSelectedCellsWithRowNumbers() {
2076     const auto& columns = selectedColumns(false);
2077     if (columns.isEmpty())
2078         return;
2079 
2080     int first = firstSelectedRow();
2081     if (first < 0)
2082         return;
2083     int last = lastSelectedRow();
2084 
2085     WAIT_CURSOR;
2086     m_spreadsheet->beginMacro(i18n("%1: fill cells with row numbers", m_spreadsheet->name()));
2087     for (auto* col_ptr : columns) {
2088         int col = m_spreadsheet->indexOfChild<Column>(col_ptr);
2089         col_ptr->setSuppressDataChangedSignal(true);
2090         switch (col_ptr->columnMode()) {
2091         case AbstractColumn::ColumnMode::Double: {
2092             QVector<double> results(last - first + 1);
2093             for (int row = first; row <= last; row++)
2094                 if (isCellSelected(row, col))
2095                     results[row - first] = row + 1;
2096                 else
2097                     results[row - first] = col_ptr->valueAt(row);
2098             col_ptr->replaceValues(first, results);
2099             break;
2100         }
2101         case AbstractColumn::ColumnMode::Integer: {
2102             QVector<int> results(last - first + 1);
2103             for (int row = first; row <= last; row++)
2104                 if (isCellSelected(row, col))
2105                     results[row - first] = row + 1;
2106                 else
2107                     results[row - first] = col_ptr->integerAt(row);
2108             col_ptr->replaceInteger(first, results);
2109             break;
2110         }
2111         case AbstractColumn::ColumnMode::BigInt: {
2112             QVector<qint64> results(last - first + 1);
2113             for (int row = first; row <= last; row++)
2114                 if (isCellSelected(row, col))
2115                     results[row - first] = row + 1;
2116                 else
2117                     results[row - first] = col_ptr->bigIntAt(row);
2118             col_ptr->replaceBigInt(first, results);
2119             break;
2120         }
2121         case AbstractColumn::ColumnMode::Text: {
2122             QVector<QString> results;
2123             for (int row = first; row <= last; row++)
2124                 if (isCellSelected(row, col))
2125                     results << QString::number(row + 1);
2126                 else
2127                     results << col_ptr->textAt(row);
2128             col_ptr->replaceTexts(first, results);
2129             break;
2130         }
2131         // TODO: handle other modes
2132         case AbstractColumn::ColumnMode::DateTime:
2133         case AbstractColumn::ColumnMode::Month:
2134         case AbstractColumn::ColumnMode::Day:
2135             break;
2136         }
2137 
2138         col_ptr->setSuppressDataChangedSignal(false);
2139         col_ptr->setChanged();
2140     }
2141     m_spreadsheet->endMacro();
2142     RESET_CURSOR;
2143 }
2144 
2145 void SpreadsheetView::fillWithRowNumbers() {
2146     const auto& columns = selectedColumns();
2147     if (columns.isEmpty())
2148         return;
2149 
2150     WAIT_CURSOR;
2151     m_spreadsheet->beginMacro(i18np("%1: fill column with row numbers", "%1: fill columns with row numbers", m_spreadsheet->name(), columns.count()));
2152 
2153     const int rows = m_spreadsheet->rowCount();
2154 
2155     QVector<int> int_data(rows);
2156     for (int i = 0; i < rows; ++i)
2157         int_data[i] = i + 1;
2158 
2159     for (auto* col : columns) {
2160         switch (col->columnMode()) {
2161         case AbstractColumn::ColumnMode::Integer:
2162             col->replaceInteger(0, int_data);
2163             break;
2164         case AbstractColumn::ColumnMode::Double:
2165         case AbstractColumn::ColumnMode::BigInt:
2166             col->setColumnMode(AbstractColumn::ColumnMode::Integer);
2167             col->replaceInteger(0, int_data);
2168             break;
2169         case AbstractColumn::ColumnMode::Text:
2170         case AbstractColumn::ColumnMode::DateTime:
2171         case AbstractColumn::ColumnMode::Day:
2172         case AbstractColumn::ColumnMode::Month:
2173             break;
2174         }
2175     }
2176 
2177     m_spreadsheet->endMacro();
2178     RESET_CURSOR;
2179 }
2180 
2181 // TODO: this function is not used currently.
2182 void SpreadsheetView::fillSelectedCellsWithRandomNumbers() {
2183     const auto& columns = selectedColumns(false);
2184     if (columns.isEmpty())
2185         return;
2186 
2187     int first = firstSelectedRow();
2188     int last = lastSelectedRow();
2189     if (first < 0)
2190         return;
2191 
2192     WAIT_CURSOR;
2193     m_spreadsheet->beginMacro(i18n("%1: fill cells with random values", m_spreadsheet->name()));
2194     QRandomGenerator rng(QTime::currentTime().msec());
2195     for (auto* col_ptr : columns) {
2196         int col = m_spreadsheet->indexOfChild<Column>(col_ptr);
2197         col_ptr->setSuppressDataChangedSignal(true);
2198         switch (col_ptr->columnMode()) {
2199         case AbstractColumn::ColumnMode::Double: {
2200             QVector<double> results(last - first + 1);
2201             for (int row = first; row <= last; row++)
2202                 if (isCellSelected(row, col))
2203                     results[row - first] = rng.generateDouble();
2204                 else
2205                     results[row - first] = col_ptr->valueAt(row);
2206             col_ptr->replaceValues(first, results);
2207             break;
2208         }
2209         case AbstractColumn::ColumnMode::Integer: {
2210             QVector<int> results(last - first + 1);
2211             for (int row = first; row <= last; row++)
2212                 if (isCellSelected(row, col))
2213                     results[row - first] = QRandomGenerator::global()->generate();
2214                 else
2215                     results[row - first] = col_ptr->integerAt(row);
2216             col_ptr->replaceInteger(first, results);
2217             break;
2218         }
2219         case AbstractColumn::ColumnMode::BigInt: {
2220             QVector<qint64> results(last - first + 1);
2221             for (int row = first; row <= last; row++)
2222                 if (isCellSelected(row, col))
2223                     results[row - first] = QRandomGenerator::global()->generate64();
2224                 else
2225                     results[row - first] = col_ptr->bigIntAt(row);
2226             col_ptr->replaceBigInt(first, results);
2227             break;
2228         }
2229         case AbstractColumn::ColumnMode::Text: {
2230             QVector<QString> results;
2231             for (int row = first; row <= last; row++)
2232                 if (isCellSelected(row, col))
2233                     results[row - first] = QString::number(QRandomGenerator::global()->generateDouble());
2234                 else
2235                     results << col_ptr->textAt(row);
2236             col_ptr->replaceTexts(first, results);
2237             break;
2238         }
2239         case AbstractColumn::ColumnMode::DateTime:
2240         case AbstractColumn::ColumnMode::Month:
2241         case AbstractColumn::ColumnMode::Day: {
2242             QVector<QDateTime> results;
2243             QDate earliestDate(1, 1, 1);
2244             QDate latestDate(2999, 12, 31);
2245             QTime midnight(0, 0, 0, 0);
2246             for (int row = first; row <= last; row++)
2247                 if (isCellSelected(row, col))
2248                     results << QDateTime(earliestDate.addDays((QRandomGenerator::global()->generateDouble()) * ((double)earliestDate.daysTo(latestDate))),
2249                                          midnight.addMSecs((QRandomGenerator::global()->generateDouble()) * 1000 * 60 * 60 * 24));
2250                 else
2251                     results << col_ptr->dateTimeAt(row);
2252             col_ptr->replaceDateTimes(first, results);
2253             break;
2254         }
2255         }
2256 
2257         col_ptr->setSuppressDataChangedSignal(false);
2258         col_ptr->setChanged();
2259     }
2260     m_spreadsheet->endMacro();
2261     RESET_CURSOR;
2262 }
2263 
2264 void SpreadsheetView::fillWithRandomValues() {
2265     const auto& columns = selectedColumns();
2266     if (columns.isEmpty())
2267         return;
2268 
2269     auto* dlg = new RandomValuesDialog(m_spreadsheet);
2270     dlg->setColumns(columns);
2271     dlg->exec();
2272 }
2273 
2274 void SpreadsheetView::fillWithEquidistantValues() {
2275     const auto& columns = selectedColumns();
2276     if (columns.isEmpty())
2277         return;
2278 
2279     auto* dlg = new EquidistantValuesDialog(m_spreadsheet);
2280     dlg->setColumns(columns);
2281     dlg->exec();
2282 }
2283 
2284 void SpreadsheetView::fillWithFunctionValues() {
2285     const auto& columns = selectedColumns();
2286     if (columns.isEmpty())
2287         return;
2288 
2289     auto* dlg = new FunctionValuesDialog(m_spreadsheet);
2290     dlg->setColumns(columns);
2291     dlg->exec();
2292 }
2293 
2294 void SpreadsheetView::fillSelectedCellsWithConstValues() {
2295     const auto& columns = selectedColumns(false);
2296     if (columns.isEmpty())
2297         return;
2298 
2299     int first = firstSelectedRow();
2300     int last = lastSelectedRow();
2301     if (first < 0)
2302         return;
2303 
2304     bool doubleOk = false;
2305     bool intOk = false;
2306     bool bigIntOk = false;
2307     bool stringOk = false;
2308     double doubleValue = 0;
2309     int intValue = 0;
2310     qint64 bigIntValue = 0;
2311     QString stringValue;
2312 
2313     m_spreadsheet->beginMacro(i18n("%1: fill cells with const values", m_spreadsheet->name()));
2314     for (auto* col_ptr : columns) {
2315         int col = m_spreadsheet->indexOfChild<Column>(col_ptr);
2316         col_ptr->setSuppressDataChangedSignal(true);
2317         switch (col_ptr->columnMode()) {
2318         case AbstractColumn::ColumnMode::Double:
2319             if (!doubleOk)
2320                 doubleValue = QInputDialog::getDouble(this,
2321                                                       i18n("Fill the selection with constant value"),
2322                                                       i18n("Value"),
2323                                                       0,
2324                                                       -std::numeric_limits<double>::max(),
2325                                                       std::numeric_limits<double>::max(),
2326                                                       6,
2327                                                       &doubleOk);
2328             if (doubleOk) {
2329                 WAIT_CURSOR;
2330                 QVector<double> results(last - first + 1);
2331                 for (int row = first; row <= last; row++) {
2332                     if (isCellSelected(row, col))
2333                         results[row - first] = doubleValue;
2334                     else
2335                         results[row - first] = col_ptr->valueAt(row);
2336                 }
2337                 col_ptr->replaceValues(first, results);
2338                 RESET_CURSOR;
2339             }
2340             break;
2341         case AbstractColumn::ColumnMode::Integer:
2342             if (!intOk)
2343                 intValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &intOk);
2344             if (intOk) {
2345                 WAIT_CURSOR;
2346                 QVector<int> results(last - first + 1);
2347                 for (int row = first; row <= last; row++) {
2348                     if (isCellSelected(row, col))
2349                         results[row - first] = intValue;
2350                     else
2351                         results[row - first] = col_ptr->integerAt(row);
2352                 }
2353                 col_ptr->replaceInteger(first, results);
2354                 RESET_CURSOR;
2355             }
2356             break;
2357         case AbstractColumn::ColumnMode::BigInt:
2358             // TODO: getBigInt()
2359             if (!bigIntOk)
2360                 bigIntValue =
2361                     QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &bigIntOk);
2362             if (bigIntOk) {
2363                 WAIT_CURSOR;
2364                 QVector<qint64> results(last - first + 1);
2365                 for (int row = first; row <= last; row++) {
2366                     if (isCellSelected(row, col))
2367                         results[row - first] = bigIntValue;
2368                     else
2369                         results[row - first] = col_ptr->bigIntAt(row);
2370                 }
2371                 col_ptr->replaceBigInt(first, results);
2372                 RESET_CURSOR;
2373             }
2374             break;
2375         case AbstractColumn::ColumnMode::Text:
2376             if (!stringOk)
2377                 stringValue =
2378                     QInputDialog::getText(this, i18n("Fill the selection with constant value"), i18n("Value"), QLineEdit::Normal, QString(), &stringOk);
2379             if (stringOk && !stringValue.isEmpty()) {
2380                 WAIT_CURSOR;
2381                 QVector<QString> results;
2382                 for (int row = first; row <= last; row++) {
2383                     if (isCellSelected(row, col))
2384                         results << stringValue;
2385                     else
2386                         results << col_ptr->textAt(row);
2387                 }
2388                 col_ptr->replaceTexts(first, results);
2389                 RESET_CURSOR;
2390             }
2391             break;
2392         // TODO: handle other modes
2393         case AbstractColumn::ColumnMode::DateTime:
2394         case AbstractColumn::ColumnMode::Month:
2395         case AbstractColumn::ColumnMode::Day:
2396             break;
2397         }
2398 
2399         col_ptr->setSuppressDataChangedSignal(false);
2400         col_ptr->setChanged();
2401     }
2402     m_spreadsheet->endMacro();
2403 }
2404 
2405 void SpreadsheetView::formatHeatmap() {
2406     auto columns = selectedColumns();
2407     if (columns.isEmpty())
2408         columns = m_spreadsheet->children<Column>();
2409 
2410     if (columns.isEmpty())
2411         return;
2412 
2413     auto* dlg = new FormattingHeatmapDialog(m_spreadsheet);
2414     dlg->setColumns(columns);
2415     if (dlg->exec() == QDialog::Accepted) {
2416         const auto& format = dlg->format();
2417         int count = columns.count();
2418         if (count > 1)
2419             m_spreadsheet->beginMacro(i18n("%1: set heatmap format", m_spreadsheet->name()));
2420         for (auto* col : columns)
2421             col->setHeatmapFormat(format);
2422         if (count > 1)
2423             m_spreadsheet->endMacro();
2424     }
2425 
2426     delete dlg;
2427 }
2428 
2429 void SpreadsheetView::removeFormat() {
2430     auto columns = selectedColumns();
2431     if (columns.isEmpty())
2432         columns = m_spreadsheet->children<Column>();
2433 
2434     int count = columns.count();
2435     if (count > 1)
2436         m_spreadsheet->beginMacro(i18n("%1: remove heatmap format", m_spreadsheet->name()));
2437     for (auto* col : columns)
2438         col->removeFormat();
2439     if (count > 1)
2440         m_spreadsheet->endMacro();
2441 }
2442 
2443 /*!
2444   Insert an empty column left to the first selected column
2445 */
2446 void SpreadsheetView::insertColumnLeft() {
2447     insertColumnsLeft(1);
2448 }
2449 
2450 /*!
2451   Insert multiple empty columns left to the firt selected column
2452 */
2453 void SpreadsheetView::insertColumnsLeft() {
2454     bool ok = false;
2455     int count = QInputDialog::getInt(nullptr,
2456                                      i18n("Insert empty columns"),
2457                                      i18n("Enter the number of columns to insert"),
2458                                      1 /*value*/,
2459                                      1 /*min*/,
2460                                      1000 /*max*/,
2461                                      1 /*step*/,
2462                                      &ok);
2463     if (!ok)
2464         return;
2465 
2466     insertColumnsLeft(count);
2467 }
2468 
2469 /*!
2470  * private helper function doing the actual insertion of columns to the left
2471  */
2472 void SpreadsheetView::insertColumnsLeft(int count) {
2473     const int first = firstSelectedColumn();
2474     m_spreadsheet->insertColumns(first, count);
2475 }
2476 
2477 /*!
2478   Insert an empty column right to the last selected column
2479 */
2480 void SpreadsheetView::insertColumnRight() {
2481     insertColumnsRight(1);
2482 }
2483 
2484 /*!
2485   Insert multiple empty columns right to the last selected column
2486 */
2487 void SpreadsheetView::insertColumnsRight() {
2488     bool ok = false;
2489     int count = QInputDialog::getInt(nullptr,
2490                                      i18n("Insert empty columns"),
2491                                      i18n("Enter the number of columns to insert"),
2492                                      1 /*value*/,
2493                                      1 /*min*/,
2494                                      1000 /*max*/,
2495                                      1 /*step*/,
2496                                      &ok);
2497     if (!ok)
2498         return;
2499 
2500     insertColumnsRight(count);
2501 }
2502 
2503 /*!
2504  * private helper function doing the actual insertion of columns to the right
2505  */
2506 // TODO: check whether this function can be rewritten to use Spreadsheet::insertColumns()
2507 void SpreadsheetView::insertColumnsRight(int count) {
2508     WAIT_CURSOR;
2509     m_spreadsheet->beginMacro(i18np("%1: insert empty column", "%1: insert empty columns", m_spreadsheet->name(), count));
2510 
2511     const int last = lastSelectedColumn();
2512     const int cols = m_spreadsheet->columnCount();
2513 
2514     if (last >= 0) {
2515         if (last < m_spreadsheet->columnCount() - 1) {
2516             // determine the column next to the last selected column
2517             Column* nextCol = m_spreadsheet->child<Column>(last + 1);
2518 
2519             for (int i = 0; i < count; ++i) {
2520                 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double);
2521                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
2522                 newCol->insertRows(0, m_spreadsheet->rowCount());
2523 
2524                 // insert the new column before the column next to the last selected column
2525                 m_spreadsheet->insertChildBefore(newCol, nextCol);
2526             }
2527         } else {
2528             for (int i = 0; i < count; ++i) {
2529                 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double);
2530                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
2531                 newCol->insertRows(0, m_spreadsheet->rowCount());
2532 
2533                 // last column selected, no next column available -> add/append a new column
2534                 if (!m_spreadsheet->statisticsSpreadsheet())
2535                     m_spreadsheet->addChild(newCol);
2536                 else
2537                     m_spreadsheet->insertChildBefore(newCol, m_spreadsheet->statisticsSpreadsheet());
2538             }
2539         }
2540     } else {
2541         if (m_spreadsheet->columnCount() > 0) {
2542             for (int i = 0; i < count; ++i) {
2543                 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double);
2544                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
2545                 newCol->insertRows(0, m_spreadsheet->rowCount());
2546 
2547                 // columns available but no columns selected -> append the new column at the very end
2548                 if (!m_spreadsheet->statisticsSpreadsheet())
2549                     m_spreadsheet->addChild(newCol);
2550                 else
2551                     m_spreadsheet->insertChildBefore(newCol, m_spreadsheet->statisticsSpreadsheet());
2552             }
2553         } else {
2554             // no columns available anymore -> resize the spreadsheet and the new column to the default size
2555             KConfigGroup group = Settings::group(QStringLiteral("Spreadsheet"));
2556             const int rows = group.readEntry(QLatin1String("RowCount"), 100);
2557             m_spreadsheet->setRowCount(rows);
2558 
2559             for (int i = 0; i < count; ++i) {
2560                 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double);
2561                 (i == 0) ? newCol->setPlotDesignation(AbstractColumn::PlotDesignation::X) : newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
2562                 newCol->insertRows(0, rows);
2563 
2564                 // add/append a new column
2565                 if (!m_spreadsheet->statisticsSpreadsheet())
2566                     m_spreadsheet->addChild(newCol);
2567                 else
2568                     m_spreadsheet->insertChildBefore(newCol, m_spreadsheet->statisticsSpreadsheet());
2569             }
2570         }
2571     }
2572 
2573     m_spreadsheet->endMacro();
2574     RESET_CURSOR;
2575 }
2576 
2577 void SpreadsheetView::removeSelectedColumns() {
2578     WAIT_CURSOR;
2579     m_spreadsheet->beginMacro(i18n("%1: remove selected columns", m_spreadsheet->name()));
2580 
2581     for (auto* column : selectedColumns())
2582         m_spreadsheet->removeChild(column);
2583 
2584     m_spreadsheet->endMacro();
2585     RESET_CURSOR;
2586 }
2587 
2588 void SpreadsheetView::clearSelectedColumns() {
2589     m_spreadsheet->clear(selectedColumns());
2590 }
2591 
2592 void SpreadsheetView::toggleFreezeColumn() {
2593     if (!m_frozenTableView) {
2594         m_frozenTableView = new QTableView(this);
2595         m_frozenTableView->setModel(m_model);
2596 
2597         auto* delegate = new SpreadsheetItemDelegate(this);
2598         connect(delegate, &SpreadsheetItemDelegate::returnPressed, this, &SpreadsheetView::advanceCell);
2599         connect(delegate, &SpreadsheetItemDelegate::editorEntered, this, [=]() {
2600             m_editorEntered = true;
2601         });
2602         connect(delegate, &SpreadsheetItemDelegate::closeEditor, this, [=]() {
2603             m_editorEntered = false;
2604         });
2605 
2606         m_frozenTableView->setItemDelegate(delegate);
2607         m_frozenTableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
2608 
2609         m_frozenTableView->setFocusPolicy(Qt::NoFocus);
2610         m_frozenTableView->verticalHeader()->hide();
2611         m_frozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
2612         m_frozenTableView->horizontalHeader()->installEventFilter(this);
2613         m_tableView->viewport()->stackUnder(m_frozenTableView);
2614 
2615         //  m_frozenTableView->setStyleSheet("QTableView { border: none;"
2616         //                              "background-color: #8EDE21;"
2617         //                              "selection-background-color: #999}");
2618         m_frozenTableView->setSelectionModel(m_tableView->selectionModel());
2619         for (int col = 1; col < m_model->columnCount(); ++col)
2620             m_frozenTableView->setColumnHidden(col, true);
2621 
2622         m_frozenTableView->setColumnWidth(0, m_tableView->columnWidth(0));
2623 
2624         m_frozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2625         m_frozenTableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
2626         m_frozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2627 
2628         updateFrozenTableGeometry();
2629 
2630         // synchronize the sroll bars across the main and the frozen views
2631         connect(m_frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged, m_tableView->verticalScrollBar(), &QAbstractSlider::setValue);
2632         connect(m_tableView->verticalScrollBar(), &QAbstractSlider::valueChanged, m_frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue);
2633     }
2634 
2635     m_frozenTableView->setVisible(!m_frozenTableView->isVisible());
2636 }
2637 
2638 void SpreadsheetView::setSelectionAs() {
2639     const auto& columns = selectedColumns();
2640     if (!columns.size())
2641         return;
2642 
2643     m_spreadsheet->beginMacro(i18n("%1: set plot designation", m_spreadsheet->name()));
2644 
2645     auto* action = dynamic_cast<QAction*>(QObject::sender());
2646     if (!action)
2647         return;
2648 
2649     auto pd = (AbstractColumn::PlotDesignation)action->data().toInt();
2650     for (auto* col : columns)
2651         col->setPlotDesignation(pd);
2652 
2653     m_spreadsheet->endMacro();
2654 }
2655 
2656 /*!
2657  * add, subtract, multiply, divide
2658  */
2659 void SpreadsheetView::modifyValues() {
2660     const auto& columns = selectedColumns();
2661     if (columns.isEmpty())
2662         return;
2663 
2664     const QAction* action = dynamic_cast<const QAction*>(QObject::sender());
2665     auto op = (AddSubtractValueDialog::Operation)action->data().toInt();
2666     auto* dlg = new AddSubtractValueDialog(m_spreadsheet, columns, op);
2667     dlg->exec();
2668 }
2669 
2670 void SpreadsheetView::reverseColumns() {
2671     const auto& columns = selectedColumns();
2672     if (columns.isEmpty())
2673         return;
2674 
2675     WAIT_CURSOR;
2676     m_spreadsheet->beginMacro(i18np("%1: reverse column", "%1: reverse columns", m_spreadsheet->name(), columns.size()));
2677     for (auto* col : columns) {
2678         if (col->columnMode() == AbstractColumn::ColumnMode::Double) {
2679             // determine the last row containing a valid value,
2680             // ignore all following empty rows when doing the reverse
2681             auto* data = static_cast<QVector<double>*>(col->data());
2682             QVector<double> new_data(*data);
2683             auto itEnd = new_data.begin();
2684             auto it = new_data.begin();
2685             while (it != new_data.end()) {
2686                 if (!std::isnan(*it))
2687                     itEnd = it;
2688                 ++it;
2689             }
2690             ++itEnd;
2691 
2692             std::reverse(new_data.begin(), itEnd);
2693             col->setValues(new_data);
2694         } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) {
2695             auto* data = static_cast<QVector<int>*>(col->data());
2696             QVector<int> new_data(*data);
2697             std::reverse(new_data.begin(), new_data.end());
2698             col->setIntegers(new_data);
2699         } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) {
2700             auto* data = static_cast<QVector<qint64>*>(col->data());
2701             QVector<qint64> new_data(*data);
2702             std::reverse(new_data.begin(), new_data.end());
2703             col->setBigInts(new_data);
2704         }
2705     }
2706     m_spreadsheet->endMacro();
2707     RESET_CURSOR;
2708 }
2709 
2710 void SpreadsheetView::dropColumnValues() {
2711     const auto& columns = selectedColumns();
2712     if (columns.isEmpty())
2713         return;
2714 
2715     auto* dlg = new DropValuesDialog(m_spreadsheet);
2716     dlg->setColumns(columns);
2717     dlg->exec();
2718 }
2719 
2720 void SpreadsheetView::maskColumnValues() {
2721     const auto& columns = selectedColumns();
2722     if (columns.isEmpty())
2723         return;
2724 
2725     auto* dlg = new DropValuesDialog(m_spreadsheet, true);
2726     dlg->setColumns(columns);
2727     dlg->exec();
2728 }
2729 
2730 void SpreadsheetView::sampleColumnValues() {
2731     const auto& columns = selectedColumns();
2732     if (columns.isEmpty())
2733         return;
2734 
2735     auto* dlg = new SampleValuesDialog(m_spreadsheet, this);
2736     dlg->setColumns(columns);
2737     dlg->exec();
2738 }
2739 
2740 void SpreadsheetView::flattenColumns() {
2741     const auto& columns = selectedColumns();
2742     if (columns.isEmpty())
2743         return;
2744 
2745     // ensure all selected columns have the same column mode
2746     auto mode = columns.at(0)->columnMode();
2747     for (auto* col : columns) {
2748         if (col->columnMode() != mode) {
2749             KMessageBox::error(this, i18n("The selected columns have different data types and cannot be flattened because of this. "));
2750             return;
2751         }
2752     }
2753 
2754     auto* dlg = new FlattenColumnsDialog(m_spreadsheet, this);
2755     dlg->setColumns(columns);
2756     dlg->exec();
2757 }
2758 
2759 // void SpreadsheetView::joinColumns() {
2760 //  //TODO
2761 // }
2762 
2763 void SpreadsheetView::normalizeSelectedColumns(QAction* action) {
2764     auto columns = selectedColumns();
2765     if (columns.isEmpty())
2766         return;
2767 
2768     auto method = static_cast<NormalizationMethod>(action->data().toInt());
2769 
2770     double rescaleIntervalMin = 0.0;
2771     double rescaleIntervalMax = 0.0;
2772     if (method == Rescale) {
2773         auto* dlg = new RescaleDialog(this);
2774         dlg->setColumns(columns);
2775         int rc = dlg->exec();
2776         if (rc != QDialog::Accepted)
2777             return;
2778 
2779         rescaleIntervalMin = dlg->min();
2780         rescaleIntervalMax = dlg->max();
2781         delete dlg;
2782     }
2783 
2784     WAIT_CURSOR;
2785     QStringList messages;
2786     auto* message = "Normalization of the column <i>%1</i> was not possible because of %2.";
2787     m_spreadsheet->beginMacro(i18n("%1: normalize columns", m_spreadsheet->name()));
2788 
2789     for (auto* col : columns) {
2790         if (col->columnMode() != AbstractColumn::ColumnMode::Double && col->columnMode() != AbstractColumn::ColumnMode::Integer
2791             && col->columnMode() != AbstractColumn::ColumnMode::BigInt)
2792             continue;
2793 
2794         if (col->columnMode() == AbstractColumn::ColumnMode::Integer || col->columnMode() == AbstractColumn::ColumnMode::BigInt)
2795             col->setColumnMode(AbstractColumn::ColumnMode::Double);
2796 
2797         auto* data = static_cast<QVector<double>*>(col->data());
2798         QVector<double> new_data(col->rowCount());
2799 
2800         switch (method) {
2801         case DivideBySum: {
2802             double sum = std::accumulate(data->begin(), data->end(), 0);
2803             if (sum != 0.0) {
2804                 for (int i = 0; i < col->rowCount(); ++i)
2805                     new_data[i] = data->operator[](i) / sum;
2806             } else {
2807                 messages << i18n(message, col->name(), i18n("Sum = 0"));
2808                 continue;
2809             }
2810             break;
2811         }
2812         case DivideByMin: {
2813             double min = col->minimum();
2814             if (min != 0.0) {
2815                 for (int i = 0; i < col->rowCount(); ++i)
2816                     new_data[i] = data->operator[](i) / min;
2817             } else {
2818                 messages << i18n(message, col->name(), i18n("Min = 0"));
2819                 continue;
2820             }
2821             break;
2822         }
2823         case DivideByMax: {
2824             double max = col->maximum();
2825             if (max != 0.0) {
2826                 for (int i = 0; i < col->rowCount(); ++i)
2827                     new_data[i] = data->operator[](i) / max;
2828             } else {
2829                 messages << i18n(message, col->name(), i18n("Max = 0"));
2830                 continue;
2831             }
2832             break;
2833         }
2834         case DivideByCount: {
2835             int count = data->size();
2836             if (count != 0.0) {
2837                 for (int i = 0; i < col->rowCount(); ++i)
2838                     new_data[i] = data->operator[](i) / count;
2839             } else {
2840                 messages << i18n(message, col->name(), i18n("Count = 0"));
2841                 continue;
2842             }
2843             break;
2844         }
2845         case DivideByMean: {
2846             double mean = col->statistics().arithmeticMean;
2847             if (mean != 0.0) {
2848                 for (int i = 0; i < col->rowCount(); ++i)
2849                     new_data[i] = data->operator[](i) / mean;
2850             } else {
2851                 messages << i18n(message, col->name(), i18n("Mean = 0"));
2852                 continue;
2853             }
2854             break;
2855         }
2856         case DivideByMedian: {
2857             double median = col->statistics().median;
2858             if (median != 0.0) {
2859                 for (int i = 0; i < col->rowCount(); ++i)
2860                     new_data[i] = data->operator[](i) / median;
2861             } else {
2862                 messages << i18n(message, col->name(), i18n("Median = 0"));
2863                 continue;
2864             }
2865             break;
2866         }
2867         case DivideByMode: {
2868             double mode = col->statistics().mode;
2869             if (mode != 0.0 && !std::isnan(mode)) {
2870                 for (int i = 0; i < col->rowCount(); ++i)
2871                     new_data[i] = data->operator[](i) / mode;
2872             } else {
2873                 if (mode == 0.0)
2874                     messages << i18n(message, col->name(), i18n("Mode = 0"));
2875                 else
2876                     messages << i18n(message, col->name(), i18n("'Mode not defined'"));
2877                 continue;
2878             }
2879             break;
2880         }
2881         case DivideByRange: {
2882             double range = col->statistics().maximum - col->statistics().minimum;
2883             if (range != 0.0) {
2884                 for (int i = 0; i < col->rowCount(); ++i)
2885                     new_data[i] = data->operator[](i) / range;
2886             } else {
2887                 messages << i18n(message, col->name(), i18n("Range = 0"));
2888                 continue;
2889             }
2890             break;
2891         }
2892         case DivideBySD: {
2893             double std = col->statistics().standardDeviation;
2894             if (std != 0.0) {
2895                 for (int i = 0; i < col->rowCount(); ++i)
2896                     new_data[i] = data->operator[](i) / std;
2897             } else {
2898                 messages << i18n(message, col->name(), i18n("SD = 0"));
2899                 continue;
2900             }
2901             break;
2902         }
2903         case DivideByMAD: {
2904             double mad = col->statistics().medianDeviation;
2905             if (mad != 0.0) {
2906                 for (int i = 0; i < col->rowCount(); ++i)
2907                     new_data[i] = data->operator[](i) / mad;
2908             } else {
2909                 messages << i18n(message, col->name(), i18n("MAD = 0"));
2910                 continue;
2911             }
2912             break;
2913         }
2914         case DivideByIQR: {
2915             double iqr = col->statistics().iqr;
2916             if (iqr != 0.0) {
2917                 for (int i = 0; i < col->rowCount(); ++i)
2918                     new_data[i] = data->operator[](i) / iqr;
2919             } else {
2920                 messages << i18n(message, col->name(), i18n("IQR = 0"));
2921                 continue;
2922             }
2923             break;
2924         }
2925         case ZScoreSD: {
2926             double mean = col->statistics().arithmeticMean;
2927             double std = col->statistics().standardDeviation;
2928             if (std != 0.0) {
2929                 for (int i = 0; i < col->rowCount(); ++i)
2930                     new_data[i] = (data->operator[](i) - mean) / std;
2931             } else {
2932                 messages << i18n(message, col->name(), i18n("SD = 0"));
2933                 continue;
2934             }
2935             break;
2936         }
2937         case ZScoreMAD: {
2938             double median = col->statistics().median;
2939             double mad = col->statistics().medianDeviation;
2940             if (mad != 0.0) {
2941                 for (int i = 0; i < col->rowCount(); ++i)
2942                     new_data[i] = (data->operator[](i) - median) / mad;
2943             } else {
2944                 messages << i18n(message, col->name(), i18n("MAD = 0"));
2945                 continue;
2946             }
2947             break;
2948         }
2949         case ZScoreIQR: {
2950             double median = col->statistics().median;
2951             double iqr = col->statistics().thirdQuartile - col->statistics().firstQuartile;
2952             if (iqr != 0.0) {
2953                 for (int i = 0; i < col->rowCount(); ++i)
2954                     new_data[i] = (data->operator[](i) - median) / iqr;
2955             } else {
2956                 messages << i18n(message, col->name(), i18n("IQR = 0"));
2957                 continue;
2958             }
2959             break;
2960         }
2961         case Rescale: {
2962             double min = col->statistics().minimum;
2963             double max = col->statistics().maximum;
2964             if (max - min != 0.0) {
2965                 for (int i = 0; i < col->rowCount(); ++i)
2966                     new_data[i] = rescaleIntervalMin + (data->operator[](i) - min) / (max - min) * (rescaleIntervalMax - rescaleIntervalMin);
2967             } else {
2968                 messages << i18n(message, col->name(), i18n("Max - Min = 0"));
2969                 continue;
2970             }
2971             break;
2972         }
2973         }
2974 
2975         col->setValues(new_data);
2976     }
2977     m_spreadsheet->endMacro();
2978     RESET_CURSOR;
2979 
2980     if (!messages.isEmpty()) {
2981         QString info;
2982         for (const QString& message : messages) {
2983             if (!info.isEmpty())
2984                 info += QLatin1String("<br><br>");
2985             info += message;
2986         }
2987         QMessageBox::warning(this, i18n("Normalization not possible"), info);
2988     }
2989 }
2990 
2991 void SpreadsheetView::powerTransformSelectedColumns(QAction* action) {
2992     auto columns = selectedColumns();
2993     if (columns.isEmpty())
2994         return;
2995 
2996     auto power = static_cast<TukeyLadderPower>(action->data().toInt());
2997 
2998     WAIT_CURSOR;
2999     m_spreadsheet->beginMacro(i18n("%1: power transform columns", m_spreadsheet->name()));
3000 
3001     for (auto* col : columns) {
3002         if (col->columnMode() != AbstractColumn::ColumnMode::Double && col->columnMode() != AbstractColumn::ColumnMode::Integer
3003             && col->columnMode() != AbstractColumn::ColumnMode::BigInt)
3004             continue;
3005 
3006         if (col->columnMode() == AbstractColumn::ColumnMode::Integer || col->columnMode() == AbstractColumn::ColumnMode::BigInt)
3007             col->setColumnMode(AbstractColumn::ColumnMode::Double);
3008 
3009         auto* data = static_cast<QVector<double>*>(col->data());
3010         QVector<double> new_data(col->rowCount());
3011 
3012         switch (power) {
3013         case InverseSquared: {
3014             for (int i = 0; i < col->rowCount(); ++i) {
3015                 double x = data->operator[](i);
3016                 if (x != 0.0)
3017                     new_data[i] = 1 / gsl_pow_2(x);
3018                 else
3019                     new_data[i] = NAN;
3020             }
3021             break;
3022         }
3023         case Inverse: {
3024             for (int i = 0; i < col->rowCount(); ++i) {
3025                 double x = data->operator[](i);
3026                 if (x != 0.0)
3027                     new_data[i] = 1 / x;
3028                 else
3029                     new_data[i] = NAN;
3030             }
3031             break;
3032         }
3033         case InverseSquareRoot: {
3034             for (int i = 0; i < col->rowCount(); ++i) {
3035                 double x = data->operator[](i);
3036                 if (x >= 0.0)
3037                     new_data[i] = 1 / std::sqrt(x);
3038                 else
3039                     new_data[i] = NAN;
3040             }
3041             break;
3042         }
3043         case Log: {
3044             for (int i = 0; i < col->rowCount(); ++i) {
3045                 double x = data->operator[](i);
3046                 if (x >= 0.0)
3047                     new_data[i] = log10(x);
3048                 else
3049                     new_data[i] = NAN;
3050             }
3051             break;
3052         }
3053         case SquareRoot: {
3054             for (int i = 0; i < col->rowCount(); ++i) {
3055                 double x = data->operator[](i);
3056                 if (x >= 0.0)
3057                     new_data[i] = std::sqrt(x);
3058                 else
3059                     new_data[i] = NAN;
3060             }
3061             break;
3062         }
3063         case Squared: {
3064             for (int i = 0; i < col->rowCount(); ++i) {
3065                 double x = data->operator[](i);
3066                 new_data[i] = gsl_pow_2(x);
3067             }
3068             break;
3069         }
3070         case Cube: {
3071             for (int i = 0; i < col->rowCount(); ++i) {
3072                 double x = data->operator[](i);
3073                 new_data[i] = gsl_pow_3(x);
3074             }
3075             break;
3076         }
3077         }
3078 
3079         col->setValues(new_data);
3080     }
3081 
3082     m_spreadsheet->endMacro();
3083     RESET_CURSOR;
3084 }
3085 
3086 // TODO: either make this complete (support all column modes and normalization methods) or remove this code completely.
3087 /*
3088 void SpreadsheetView::normalizeSelection() {
3089     WAIT_CURSOR;
3090     m_spreadsheet->beginMacro(i18n("%1: normalize selection", m_spreadsheet->name()));
3091     double max = 0.0;
3092     for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++)
3093         if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Double)
3094             for (int row = 0; row < m_spreadsheet->rowCount(); row++) {
3095                 if (isCellSelected(row, col) && m_spreadsheet->column(col)->valueAt(row) > max)
3096                     max = m_spreadsheet->column(col)->valueAt(row);
3097             }
3098 
3099     if (max != 0.0) { // avoid division by zero
3100         //TODO setSuppressDataChangedSignal
3101         for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++)
3102             if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Double)
3103                 for (int row = 0; row < m_spreadsheet->rowCount(); row++) {
3104                     if (isCellSelected(row, col))
3105                         m_spreadsheet->column(col)->setValueAt(row, m_spreadsheet->column(col)->valueAt(row) / max);
3106                 }
3107     }
3108     m_spreadsheet->endMacro();
3109     RESET_CURSOR;
3110 }
3111 */
3112 
3113 void SpreadsheetView::showAllColumnsStatistics() {
3114     showColumnStatistics(true);
3115 }
3116 
3117 void SpreadsheetView::showColumnStatistics(bool forAll) {
3118     QString dlgTitle(i18n("%1: column statistics", m_spreadsheet->name()));
3119     QVector<Column*> columns;
3120 
3121     // Column statistics can be shown for:
3122     // * all columns in the spreadsheet - called via the action_statistics_all_columns or when one single cell is selected (=no selection)
3123     // * all selected columns in the speadsheet - called via the context menu of the header
3124     // * selected column cells - called via the context menu in the spreadsheet with multiple selected cells
3125     const auto& indexes = m_tableView->selectionModel()->selectedIndexes();
3126     if (forAll || indexes.size() <= 1) {
3127         for (int col = 0; col < m_spreadsheet->columnCount(); ++col) {
3128             auto* column = m_spreadsheet->column(col);
3129             if (column->hasValues())
3130                 columns << column;
3131         }
3132     } else {
3133         columns = selectedColumns(true);
3134 
3135         // if no columns are fully selected, copy the selected cells into new Columns which will be processes in the statistics dialog
3136         if (columns.isEmpty()) {
3137             const auto& children = m_spreadsheet->children<Column>();
3138             Column* targetColumn{nullptr};
3139             QMap<int, int> columnMappings; // key = child column index in the spreadsheet, value = column index in the vector of new colums
3140             QMap<int, int> rowMappings; // key = child column index in the spreadsheet, value = last row index
3141             for (const auto& index : indexes) {
3142                 int col = index.column();
3143                 int row = index.row();
3144                 const auto* sourceColumn = children.at(col);
3145 
3146                 if (columnMappings.contains(col))
3147                     targetColumn = columns.at(columnMappings[col]);
3148                 else {
3149                     targetColumn = new Column(i18n("Selection in %1", sourceColumn->name()), sourceColumn->columnMode());
3150                     columns << targetColumn;
3151                     columnMappings[col] = columns.count() - 1;
3152                     rowMappings[col] = 0;
3153                 }
3154 
3155                 int targetRow = rowMappings[col];
3156 
3157                 switch (targetColumn->columnMode()) {
3158                 case AbstractColumn::ColumnMode::Double:
3159                     targetColumn->setValueAt(targetRow, sourceColumn->valueAt(row));
3160                     break;
3161                 case AbstractColumn::ColumnMode::Integer:
3162                     targetColumn->setIntegerAt(targetRow, sourceColumn->integerAt(row));
3163                     break;
3164                 case AbstractColumn::ColumnMode::BigInt:
3165                     targetColumn->setBigIntAt(targetRow, sourceColumn->bigIntAt(row));
3166                     break;
3167                 case AbstractColumn::ColumnMode::Text:
3168                     targetColumn->setTextAt(targetRow, sourceColumn->textAt(row));
3169                     break;
3170                 case AbstractColumn::ColumnMode::DateTime:
3171                 case AbstractColumn::ColumnMode::Month:
3172                 case AbstractColumn::ColumnMode::Day:
3173                     targetColumn->setDateTimeAt(targetRow, sourceColumn->dateTimeAt(row));
3174                     break;
3175                 }
3176 
3177                 rowMappings[col] = targetRow + 1;
3178             }
3179         }
3180     }
3181 
3182     auto* dlg = new StatisticsDialog(dlgTitle, columns);
3183     dlg->setModal(true);
3184     dlg->show();
3185     QApplication::processEvents(QEventLoop::AllEvents, 0);
3186     QTimer::singleShot(0, this, [=]() {
3187         dlg->showStatistics();
3188     });
3189 }
3190 
3191 void SpreadsheetView::showRowStatistics() {
3192     QString dlgTitle(i18n("%1: row statistics", m_spreadsheet->name()));
3193 
3194     QVector<Column*> columns;
3195     const auto& rows = m_tableView->selectionModel()->selectedRows();
3196 
3197     for (int i = 0; i < rows.count(); ++i) {
3198         int row = rows.at(i).row();
3199         QVector<double> rowValues;
3200         for (int j = 0; j < m_spreadsheet->columnCount(); ++j)
3201             rowValues << m_spreadsheet->column(j)->valueAt(row);
3202         columns << new Column(i18n("Row %1", row + 1), rowValues);
3203     }
3204 
3205     auto* dlg = new StatisticsDialog(dlgTitle, columns);
3206     dlg->showStatistics();
3207 
3208     if (dlg->exec() == QDialog::Accepted) {
3209         qDeleteAll(columns);
3210         columns.clear();
3211     }
3212 }
3213 
3214 /*!
3215   Insert an empty row above(=before) the first selected row
3216 */
3217 void SpreadsheetView::insertRowAbove() {
3218     insertRowsAbove(1);
3219 }
3220 
3221 /*!
3222   Insert multiple empty rows above(=before) the first selected row
3223 */
3224 void SpreadsheetView::insertRowsAbove() {
3225     bool ok = false;
3226     int count = QInputDialog::getInt(nullptr,
3227                                      i18n("Insert multiple rows"),
3228                                      i18n("Enter the number of rows to insert"),
3229                                      1 /*value*/,
3230                                      1 /*min*/,
3231                                      1000000 /*max*/,
3232                                      1 /*step*/,
3233                                      &ok);
3234     if (ok)
3235         insertRowsAbove(count);
3236 }
3237 
3238 /*!
3239  * private helper function doing the actual insertion of rows above
3240  */
3241 void SpreadsheetView::insertRowsAbove(int count) {
3242     int first = firstSelectedRow();
3243     if (first < 0)
3244         return;
3245 
3246     WAIT_CURSOR;
3247     m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count));
3248     m_spreadsheet->insertRows(first, count);
3249     m_spreadsheet->endMacro();
3250     RESET_CURSOR;
3251 }
3252 
3253 /*!
3254   Insert an empty row below the last selected row
3255 */
3256 void SpreadsheetView::insertRowBelow() {
3257     insertRowsBelow(1);
3258 }
3259 
3260 /*!
3261   Insert an empty row below the last selected row
3262 */
3263 void SpreadsheetView::insertRowsBelow() {
3264     bool ok = false;
3265     int count = QInputDialog::getInt(nullptr,
3266                                      i18n("Insert multiple rows"),
3267                                      i18n("Enter the number of rows to insert"),
3268                                      1 /*value*/,
3269                                      1 /*min*/,
3270                                      1000000 /*max*/,
3271                                      1 /*step*/,
3272                                      &ok);
3273     if (ok)
3274         insertRowsBelow(count);
3275 }
3276 
3277 /*!
3278  * private helper function doing the actual insertion of rows below
3279  */
3280 void SpreadsheetView::insertRowsBelow(int count) {
3281     int last = lastSelectedRow();
3282     if (last < 0)
3283         return;
3284 
3285     WAIT_CURSOR;
3286     m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count));
3287 
3288     if (last < m_spreadsheet->rowCount() - 1)
3289         m_spreadsheet->insertRows(last + 1, count); // insert before the next to the last selected row
3290     else
3291         m_spreadsheet->appendRows(count); // append new rows at the end
3292 
3293     m_spreadsheet->endMacro();
3294     RESET_CURSOR;
3295 }
3296 
3297 void SpreadsheetView::removeSelectedRows() {
3298     if (firstSelectedRow() < 0)
3299         return;
3300 
3301     WAIT_CURSOR;
3302     m_spreadsheet->beginMacro(i18n("%1: remove selected rows", m_spreadsheet->name()));
3303     // TODO setSuppressDataChangedSignal
3304     for (const auto& i : selectedRows().intervals())
3305         m_spreadsheet->removeRows(i.start(), i.size());
3306     m_spreadsheet->endMacro();
3307     RESET_CURSOR;
3308 }
3309 
3310 void SpreadsheetView::clearSelectedCells() {
3311     // don't try to clear values if the selected cells don't have any values at all
3312     bool empty = true;
3313 
3314     const auto& columns = m_spreadsheet->children<Column>();
3315     const auto& indexes = m_tableView->selectionModel()->selectedIndexes();
3316     for (const auto& index : indexes) {
3317         if (columns.at(index.column())->isValid(index.row())) {
3318             empty = false;
3319             break;
3320         }
3321         if (!empty)
3322             break;
3323     }
3324 
3325     if (empty)
3326         return;
3327 
3328     WAIT_CURSOR;
3329     m_spreadsheet->beginMacro(i18n("%1: clear selected cells", m_spreadsheet->name()));
3330     for (auto* column : columns) {
3331         column->setSuppressDataChangedSignal(true);
3332         //      if (formulaModeActive()) {
3333         //          int col = m_spreadsheet->indexOfChild<Column>(column);
3334         //          for (int row = last; row >= first; row--)
3335         //              if (isCellSelected(row, col))
3336         //                  column->setFormula(row, QString());
3337         //      } else {
3338         int index = m_spreadsheet->indexOfChild<Column>(column);
3339         if (isColumnSelected(index, true)) {
3340             // if the whole column is selected, clear directly instead of looping over the rows
3341             column->clear();
3342         } else {
3343             for (const auto& index : indexes)
3344                 columns.at(index.column())->asStringColumn()->setTextAt(index.row(), QString());
3345         }
3346 
3347         column->setSuppressDataChangedSignal(false);
3348         column->setChanged();
3349     }
3350     m_spreadsheet->endMacro();
3351     RESET_CURSOR;
3352 }
3353 
3354 void SpreadsheetView::goToCell() {
3355     auto* dlg = new GoToDialog(this);
3356     if (dlg->exec() == QDialog::Accepted) {
3357         int row = dlg->row();
3358         if (row < 1)
3359             row = 1;
3360         if (row > m_spreadsheet->rowCount())
3361             row = m_spreadsheet->rowCount();
3362 
3363         int col = dlg->column();
3364         if (col < 1)
3365             col = 1;
3366         if (col > m_spreadsheet->columnCount())
3367             col = m_spreadsheet->columnCount();
3368 
3369         goToCell(row - 1, col - 1);
3370     }
3371     delete dlg;
3372 }
3373 
3374 void SpreadsheetView::searchReplace() {
3375     const auto* action = dynamic_cast<const QAction*>(QObject::sender());
3376     if (action == action_search_replace)
3377         showSearchReplace(true); // search&replace mode
3378     else
3379         showSearchReplace(false); // search mode
3380 }
3381 
3382 void SpreadsheetView::showSearchReplace(bool replace) {
3383     if (!m_searchReplaceWidget) {
3384         m_searchReplaceWidget = new SearchReplaceWidget(m_spreadsheet, this);
3385         static_cast<QVBoxLayout*>(this->layout())->addWidget(m_searchReplaceWidget);
3386     }
3387 
3388     m_searchReplaceWidget->setReplaceEnabled(replace);
3389 
3390     const auto& indexes = m_tableView->selectionModel()->selectedIndexes();
3391     if (!indexes.isEmpty()) {
3392         const auto& firstIndex = indexes.constFirst();
3393         const auto* column = m_spreadsheet->column(firstIndex.column());
3394         const int row = firstIndex.row();
3395         m_searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row));
3396     }
3397 
3398     m_searchReplaceWidget->show();
3399 
3400     // interrupt the event loop to show/render the widget first and set the focus
3401     // after this otherwise the focus in the search field is not set
3402     QTimer::singleShot(0, this, [=]() {
3403         m_searchReplaceWidget->setFocus();
3404     });
3405 }
3406 
3407 /*!
3408  * sorts the selected columns separately of each other in ascending order.
3409  */
3410 void SpreadsheetView::sortAscending() {
3411     const auto& cols = selectedColumns();
3412     if (cols.isEmpty() || !hasValues(cols))
3413         return;
3414 
3415     for (auto* col : cols)
3416         col->setSuppressDataChangedSignal(true);
3417     m_spreadsheet->sortColumns(nullptr, cols, true);
3418     for (auto* col : cols) {
3419         col->setSuppressDataChangedSignal(false);
3420         col->setChanged();
3421     }
3422 }
3423 
3424 /*!
3425  * sorts the selected columns separately of each other in descending order.
3426  */
3427 void SpreadsheetView::sortDescending() {
3428     const auto& cols = selectedColumns();
3429     if (cols.isEmpty() || !hasValues(cols))
3430         return;
3431 
3432     for (auto* col : cols)
3433         col->setSuppressDataChangedSignal(true);
3434     m_spreadsheet->sortColumns(nullptr, cols, false);
3435     for (auto* col : cols) {
3436         col->setSuppressDataChangedSignal(false);
3437         col->setChanged();
3438     }
3439 }
3440 
3441 /*!
3442  * custom sort of all or selected columns in the spreadsheet together by allowing
3443  * to specify the "sort by"-column(s) in the SortDialog.
3444  */
3445 void SpreadsheetView::sortCustom() {
3446     const auto& cols = selectedColumns();
3447     QVector<Column*> columnsToSort;
3448     Column* leadingColumn{nullptr};
3449 
3450     bool sortAll{true};
3451     if (cols.size() == 0)
3452         // no columns selected, the function is being called from the spreadsheet context menu,
3453         // sort all columns
3454         columnsToSort = m_spreadsheet->children<Column>();
3455     else if (cols.size() == 1) {
3456         // one single column selected, sort the whole spreadsheet (all columns)
3457         // with the current selected column being the leading column
3458         columnsToSort = m_spreadsheet->children<Column>();
3459         leadingColumn = cols.constFirst();
3460     } else {
3461         columnsToSort = cols;
3462         sortAll = (cols.size() == m_spreadsheet->columnCount());
3463     }
3464 
3465     // don't do any sort if there are no values yet in the columns
3466     if (!hasValues(columnsToSort))
3467         return;
3468 
3469     for (auto* col : columnsToSort)
3470         col->setSuppressDataChangedSignal(true);
3471 
3472     auto* dlg = new SortDialog(this, sortAll);
3473     connect(dlg, &SortDialog::sort, m_spreadsheet, &Spreadsheet::sortColumns);
3474     dlg->setColumns(columnsToSort, leadingColumn);
3475 
3476     int rc = dlg->exec();
3477 
3478     for (auto* col : columnsToSort) {
3479         col->setSuppressDataChangedSignal(false);
3480         if (rc == QDialog::Accepted)
3481             col->setChanged();
3482     }
3483 }
3484 
3485 bool SpreadsheetView::hasValues(const QVector<Column*> cols) {
3486     bool hasValues = false;
3487     for (const auto* col : cols) {
3488         if (col->hasValues()) {
3489             hasValues = true;
3490             break;
3491         }
3492     }
3493 
3494     return hasValues;
3495 }
3496 
3497 /*!
3498   Cause a repaint of the header.
3499 */
3500 void SpreadsheetView::updateHeaderGeometry(Qt::Orientation o, int first, int last) {
3501     // TODO
3502     if (o != Qt::Horizontal || last < first)
3503         return;
3504     m_tableView->horizontalHeader()->setStretchLastSection(true); // ugly hack (flaw in Qt? Does anyone know a better way?)
3505     m_tableView->horizontalHeader()->updateGeometry();
3506     m_tableView->horizontalHeader()->setStretchLastSection(false); // ugly hack part 2
3507 }
3508 
3509 /*!
3510   selects the column \c column in the speadsheet view .
3511 */
3512 void SpreadsheetView::selectColumn(int column) {
3513     const auto& index = m_model->index(0, column);
3514     m_tableView->scrollTo(index);
3515     QItemSelection selection(index, m_model->index(m_spreadsheet->rowCount() - 1, column));
3516     m_suppressSelectionChangedEvent = true;
3517     m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select);
3518     m_suppressSelectionChangedEvent = false;
3519 }
3520 
3521 /*!
3522   deselects the column \c column in the speadsheet view .
3523 */
3524 void SpreadsheetView::deselectColumn(int column) {
3525     QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount() - 1, column));
3526     m_suppressSelectionChangedEvent = true;
3527     m_tableView->selectionModel()->select(selection, QItemSelectionModel::Deselect);
3528     m_suppressSelectionChangedEvent = false;
3529 }
3530 
3531 /*!
3532   called when a column in the speadsheet view was clicked (click in the header).
3533   Propagates the selection of the column to the \c Spreadsheet object
3534   (a click in the header always selects the column).
3535 */
3536 void SpreadsheetView::columnClicked(int column) {
3537     m_spreadsheet->setColumnSelectedInView(column, true);
3538 }
3539 
3540 /*!
3541   called on selections changes. Propagates the selection/deselection of columns to the \c Spreadsheet object.
3542 */
3543 void SpreadsheetView::selectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) {
3544     if (m_suppressSelectionChangedEvent)
3545         return;
3546 
3547     auto* selModel = m_tableView->selectionModel();
3548     for (int i = 0; i < m_spreadsheet->columnCount(); i++)
3549         m_spreadsheet->setColumnSelectedInView(i, selModel->isColumnSelected(i));
3550 
3551     // determine the number of selected cells, columns, missing values and masked values in the current selection and show this information in the status bar.
3552     const auto& indexes = m_tableView->selectionModel()->selectedIndexes();
3553     QString resultString = QString();
3554     if (indexes.empty() || indexes.count() == 1) {
3555         Q_EMIT m_spreadsheet->statusInfo(resultString);
3556         return;
3557     }
3558 
3559     QPair<int, int> selectedRowCol = qMakePair(selectedRowCount(false), selectedColumnCount(false));
3560     const auto& columns = m_spreadsheet->children<Column>();
3561     int maskedValuesCount = 0;
3562     int missingValuesCount = 0;
3563     int selectedCellsCount = 0;
3564     for (const auto& index : indexes) {
3565         const int col = index.column();
3566         const int row = index.row();
3567         selectedCellsCount++;
3568         auto& column = columns.at(col);
3569         if (!column->isValid(row))
3570             missingValuesCount++;
3571         if (column->isMasked(row))
3572             maskedValuesCount++;
3573     }
3574 
3575     QString selectedRowsText = i18np("row", "rows", selectedRowCol.first);
3576     QString selectedColumnsText = i18np("column", "columns", selectedRowCol.second);
3577     QString selectedCellsText = i18n("cells");
3578     QString maskedValuesText = (maskedValuesCount == 1) ? i18n("masked value") : (!maskedValuesCount) ? QString() : i18n("masked values");
3579     QString missingValuesText = (missingValuesCount == 1) ? i18n("missing value") : (!missingValuesCount) ? QString() : i18n("missing values");
3580     QString maskedValuesCountText = (!maskedValuesCount) ? QString() : i18n(" , ") + i18n("%1", maskedValuesCount);
3581     QString missingValuesCountText = (!missingValuesCount) ? QString() : i18n(", ") + i18n("%1", missingValuesCount);
3582 
3583     if (selectedCellsCount == selectedRowCol.first * selectedRowCol.second)
3584         resultString = i18n("Selected: %1 %2 , %3 %4%5 %6 %7 %8",
3585                             selectedRowCol.first,
3586                             selectedRowsText,
3587                             selectedRowCol.second,
3588                             selectedColumnsText,
3589                             maskedValuesCountText,
3590                             maskedValuesText,
3591                             missingValuesCountText,
3592                             missingValuesText);
3593 
3594     else
3595         resultString = i18n("Selected: %1 %2%3 %4 %5 %6",
3596                             selectedCellsCount,
3597                             selectedCellsText,
3598                             maskedValuesCountText,
3599                             maskedValuesText,
3600                             missingValuesCountText,
3601                             missingValuesText);
3602 
3603     Q_EMIT m_spreadsheet->statusInfo(resultString);
3604 }
3605 
3606 bool SpreadsheetView::exportView() {
3607     auto* dlg = new ExportSpreadsheetDialog(this);
3608     dlg->setProjectFileName(m_spreadsheet->project()->fileName());
3609     dlg->setFileName(m_spreadsheet->name());
3610 #ifdef HAVE_FITS
3611     dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table"));
3612 #endif
3613     for (int i = 0; i < m_spreadsheet->columnCount(); ++i) {
3614         if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::ColumnMode::Double) {
3615             dlg->setExportToImage(false);
3616             break;
3617         }
3618     }
3619     if (selectedColumnCount(false /* partial selection */) == 0)
3620         dlg->setExportSelection(false);
3621 
3622     bool ret;
3623     if ((ret = dlg->exec()) == QDialog::Accepted) {
3624         const QString path = dlg->path();
3625         const bool exportHeader = dlg->exportHeader();
3626         WAIT_CURSOR;
3627         switch (dlg->format()) {
3628         case ExportSpreadsheetDialog::Format::ASCII: {
3629             const QString separator = dlg->separator();
3630             const QLocale::Language format = dlg->numberFormat();
3631             exportToFile(path, exportHeader, separator, format);
3632             break;
3633         }
3634         case ExportSpreadsheetDialog::Format::LaTeX: {
3635             const bool exportLatexHeader = dlg->exportLatexHeader();
3636             const bool gridLines = dlg->gridLines();
3637             const bool captions = dlg->captions();
3638             const bool skipEmptyRows = dlg->skipEmptyRows();
3639             const bool exportEntire = dlg->entireSpreadheet();
3640             exportToLaTeX(path, exportHeader, gridLines, captions, exportLatexHeader, skipEmptyRows, exportEntire);
3641             break;
3642         }
3643         case ExportSpreadsheetDialog::Format::FITS: {
3644 #ifdef HAVE_FITS
3645             const int exportTo = dlg->exportToFits();
3646             const bool commentsAsUnits = dlg->commentsAsUnitsFits();
3647             exportToFits(path, exportTo, commentsAsUnits);
3648 #endif
3649             break;
3650         }
3651         case ExportSpreadsheetDialog::Format::XLSX:
3652             exportToXLSX(path, exportHeader);
3653             break;
3654         case ExportSpreadsheetDialog::Format::SQLite:
3655             exportToSQLite(path);
3656             break;
3657         }
3658         RESET_CURSOR;
3659     }
3660     delete dlg;
3661 
3662     return ret;
3663 }
3664 
3665 bool SpreadsheetView::printView() {
3666     QPrinter printer;
3667     auto* dlg = new QPrintDialog(&printer, this);
3668     dlg->setWindowTitle(i18nc("@title:window", "Print Spreadsheet"));
3669 
3670     bool ret;
3671     if ((ret = dlg->exec()) == QDialog::Accepted) {
3672         print(&printer);
3673     }
3674     delete dlg;
3675     return ret;
3676 }
3677 
3678 bool SpreadsheetView::printPreview() {
3679     auto* dlg = new QPrintPreviewDialog(this);
3680     connect(dlg, &QPrintPreviewDialog::paintRequested, this, &SpreadsheetView::print);
3681     return dlg->exec();
3682 }
3683 
3684 /*!
3685   prints the complete spreadsheet to \c printer.
3686  */
3687 void SpreadsheetView::print(QPrinter* printer) const {
3688     WAIT_CURSOR;
3689     QPainter painter(printer);
3690 
3691     const int dpiy = printer->logicalDpiY();
3692     const int margin = (int)((1 / GSL_CONST_CGS_INCH) * dpiy); // 1 cm margins
3693 
3694     QHeaderView* hHeader = m_tableView->horizontalHeader();
3695     QHeaderView* vHeader = m_tableView->verticalHeader();
3696 
3697     const int rows = m_spreadsheet->rowCount();
3698     const int cols = m_spreadsheet->columnCount();
3699     int height = margin;
3700     const int vertHeaderWidth = vHeader->width();
3701     const int width = printer->pageLayout().paintRectPixels(printer->resolution()).width() - 2 * margin;
3702 
3703     int columnsPerTable = 0;
3704     int headerStringWidth = 0;
3705     int firstRowStringWidth = 0;
3706     bool tablesNeeded = false;
3707     for (int col = 0; col < cols; ++col) {
3708         headerStringWidth += m_tableView->columnWidth(col);
3709         firstRowStringWidth += m_spreadsheet->column(col)->asStringColumn()->textAt(0).length();
3710         if ((headerStringWidth >= width) || (firstRowStringWidth >= width)) {
3711             tablesNeeded = true;
3712             break;
3713         }
3714         columnsPerTable++;
3715     }
3716 
3717     int tablesCount = (columnsPerTable != 0) ? cols / columnsPerTable : 0;
3718     const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols;
3719 
3720     if (!tablesNeeded) {
3721         tablesCount = 1;
3722         columnsPerTable = cols;
3723     }
3724 
3725     if (remainingColumns > 0)
3726         tablesCount++;
3727     // Paint the horizontal header first
3728     for (int table = 0; table < tablesCount; ++table) {
3729         int right = margin + vertHeaderWidth;
3730 
3731         painter.setFont(hHeader->font());
3732         QString headerString = m_tableView->model()->headerData(0, Qt::Horizontal).toString();
3733         QRect br;
3734         br = painter.boundingRect(br, Qt::AlignCenter, headerString);
3735         QRect tr(br);
3736         if (table != 0)
3737             height += tr.height();
3738         painter.drawLine(right, height, right, height + br.height());
3739 
3740         int i = table * columnsPerTable;
3741         int toI = table * columnsPerTable + columnsPerTable;
3742         if ((remainingColumns > 0) && (table == tablesCount - 1)) {
3743             i = (tablesCount - 1) * columnsPerTable;
3744             toI = (tablesCount - 1) * columnsPerTable + remainingColumns;
3745         }
3746 
3747         for (; i < toI; ++i) {
3748             headerString = m_tableView->model()->headerData(i, Qt::Horizontal).toString();
3749             const int w = m_tableView->columnWidth(i);
3750             tr.setTopLeft(QPoint(right, height));
3751             tr.setWidth(w);
3752             tr.setHeight(br.height());
3753 
3754             painter.drawText(tr, Qt::AlignCenter, headerString);
3755             right += w;
3756             painter.drawLine(right, height, right, height + tr.height());
3757         }
3758 
3759         painter.drawLine(margin + vertHeaderWidth, height, right - 1, height); // first horizontal line
3760         height += tr.height();
3761         painter.drawLine(margin, height, right - 1, height);
3762 
3763         // print table values
3764         for (i = 0; i < rows; ++i) {
3765             right = margin;
3766             QString cellText = m_tableView->model()->headerData(i, Qt::Vertical).toString() + QLatin1Char('\t');
3767             tr = painter.boundingRect(tr, Qt::AlignCenter, cellText);
3768             painter.drawLine(right, height, right, height + tr.height());
3769 
3770             br.setTopLeft(QPoint(right, height));
3771             br.setWidth(vertHeaderWidth);
3772             br.setHeight(tr.height());
3773             painter.drawText(br, Qt::AlignCenter, cellText);
3774             right += vertHeaderWidth;
3775             painter.drawLine(right, height, right, height + tr.height());
3776             int j = table * columnsPerTable;
3777             int toJ = table * columnsPerTable + columnsPerTable;
3778             if ((remainingColumns > 0) && (table == tablesCount - 1)) {
3779                 j = (tablesCount - 1) * columnsPerTable;
3780                 toJ = (tablesCount - 1) * columnsPerTable + remainingColumns;
3781             }
3782             for (; j < toJ; j++) {
3783                 int w = m_tableView->columnWidth(j);
3784                 cellText = m_spreadsheet->column(j)->isValid(i) ? m_spreadsheet->text(i, j) + QStringLiteral("\t") : QStringLiteral("- \t");
3785                 tr = painter.boundingRect(tr, Qt::AlignCenter, cellText);
3786                 br.setTopLeft(QPoint(right, height));
3787                 br.setWidth(w);
3788                 br.setHeight(tr.height());
3789                 painter.drawText(br, Qt::AlignCenter, cellText);
3790                 right += w;
3791                 painter.drawLine(right, height, right, height + tr.height());
3792             }
3793             height += br.height();
3794             painter.drawLine(margin, height, right - 1, height);
3795 
3796             if (height >= printer->height() - margin) {
3797                 printer->newPage();
3798                 height = margin;
3799                 painter.drawLine(margin, height, right, height);
3800             }
3801         }
3802     }
3803     RESET_CURSOR;
3804 }
3805 
3806 /*!
3807  * the spreadsheet can have empty rows at the end full with NaNs.
3808  * for the export we only need to export valid (non-empty) rows.
3809  * this functions determines the maximal row to export, or -1
3810  * if the spreadsheet doesn't have any data yet.
3811  */
3812 int SpreadsheetView::maxRowToExport() const {
3813     int maxRow = -1;
3814     for (int j = 0; j < m_spreadsheet->columnCount(); ++j) {
3815         Column* col = m_spreadsheet->column(j);
3816         auto mode = col->columnMode();
3817         if (mode == AbstractColumn::ColumnMode::Double) {
3818             for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
3819                 if (!std::isnan(col->valueAt(i)) && i > maxRow)
3820                     maxRow = i;
3821             }
3822         }
3823         if (mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt) {
3824             // TODO:
3825             // integer column found. Since empty integer cells are equal to 0
3826             // at the moment, we need to export the whole column.
3827             // this logic needs to be adjusted once we're able to descriminate
3828             // between empty and 0 values for integer columns
3829             maxRow = m_spreadsheet->rowCount() - 1;
3830             break;
3831         } else if (mode == AbstractColumn::ColumnMode::DateTime) {
3832             for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
3833                 if (col->dateTimeAt(i).isValid() && i > maxRow)
3834                     maxRow = i;
3835             }
3836         } else if (mode == AbstractColumn::ColumnMode::Text) {
3837             for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
3838                 if (!col->textAt(i).isEmpty() && i > maxRow)
3839                     maxRow = i;
3840             }
3841         }
3842     }
3843 
3844     return maxRow;
3845 }
3846 
3847 void SpreadsheetView::exportToFile(const QString& path, const bool exportHeader, const QString& separator, QLocale::Language language) const {
3848     QFile file(path);
3849     if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
3850         RESET_CURSOR;
3851         QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path));
3852         return;
3853     }
3854 
3855     PERFTRACE(QStringLiteral("export spreadsheet to file"));
3856     QTextStream out(&file);
3857 
3858     int maxRow = maxRowToExport();
3859     if (maxRow < 0)
3860         return;
3861 
3862     const int cols = m_spreadsheet->columnCount();
3863     QString sep = separator;
3864     sep = sep.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive);
3865     sep = sep.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive);
3866 
3867     // export header (column names)
3868     if (exportHeader) {
3869         for (int j = 0; j < cols; ++j) {
3870             out << '"' << m_spreadsheet->column(j)->name() << '"';
3871             if (j != cols - 1)
3872                 out << sep;
3873         }
3874         out << '\n';
3875     }
3876 
3877     // export values
3878     QLocale locale(language);
3879     for (int i = 0; i <= maxRow; ++i) {
3880         for (int j = 0; j < cols; ++j) {
3881             Column* col = m_spreadsheet->column(j);
3882             if (col->columnMode() == AbstractColumn::ColumnMode::Double) {
3883                 const Double2StringFilter* out_fltr = static_cast<Double2StringFilter*>(col->outputFilter());
3884                 out << locale.toString(col->valueAt(i), out_fltr->numericFormat(), 16); // export with max. precision
3885             } else
3886                 out << col->asStringColumn()->textAt(i);
3887 
3888             if (j != cols - 1)
3889                 out << sep;
3890         }
3891         out << '\n';
3892     }
3893 }
3894 
3895 void SpreadsheetView::exportToLaTeX(const QString& path,
3896                                     const bool exportHeaders,
3897                                     const bool gridLines,
3898                                     const bool captions,
3899                                     const bool latexHeaders,
3900                                     const bool skipEmptyRows,
3901                                     const bool exportEntire) const {
3902     QFile file(path);
3903     if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
3904         RESET_CURSOR;
3905         QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path));
3906         return;
3907     }
3908 
3909     QList<Column*> toExport;
3910     int cols;
3911     int totalRowCount = 0;
3912     if (exportEntire) {
3913         cols = const_cast<SpreadsheetView*>(this)->m_spreadsheet->columnCount();
3914         totalRowCount = m_spreadsheet->rowCount();
3915         for (int col = 0; col < cols; ++col)
3916             toExport << m_spreadsheet->column(col);
3917     } else {
3918         cols = const_cast<SpreadsheetView*>(this)->selectedColumnCount(false /* partial selection */);
3919         const int firtsSelectedCol = const_cast<SpreadsheetView*>(this)->firstSelectedColumn();
3920         bool rowsCalculated = false;
3921         for (int col = firtsSelectedCol; col < firtsSelectedCol + cols; ++col) {
3922             QVector<QString> textData;
3923             for (int row = 0; row < m_spreadsheet->rowCount(); ++row) {
3924                 if (const_cast<SpreadsheetView*>(this)->isRowSelected(row)) {
3925                     textData << m_spreadsheet->column(col)->asStringColumn()->textAt(row);
3926                     if (!rowsCalculated)
3927                         totalRowCount++;
3928                 }
3929             }
3930             if (!rowsCalculated)
3931                 rowsCalculated = true;
3932             Column* column = new Column(m_spreadsheet->column(col)->name(), textData);
3933             toExport << column;
3934         }
3935     }
3936     int columnsStringSize = 0;
3937     int columnsPerTable = 0;
3938 
3939     for (int i = 0; i < cols; ++i) {
3940         int maxSize = -1;
3941         for (int j = 0; j < toExport.at(i)->asStringColumn()->rowCount(); ++j) {
3942             if (toExport.at(i)->asStringColumn()->textAt(j).size() > maxSize)
3943                 maxSize = toExport.at(i)->asStringColumn()->textAt(j).size();
3944         }
3945         columnsStringSize += maxSize;
3946         if (!toExport.at(i)->isValid(0))
3947             columnsStringSize += 3;
3948         if (columnsStringSize > 65)
3949             break;
3950         ++columnsPerTable;
3951     }
3952 
3953     const int tablesCount = (columnsPerTable != 0) ? cols / columnsPerTable : 0;
3954     const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols;
3955 
3956     bool columnsSeparating = (cols > columnsPerTable);
3957     QTextStream out(&file);
3958 
3959     const QString latexFullPath = QStandardPaths::findExecutable(QLatin1String("latex"));
3960     if (latexFullPath.isEmpty()) {
3961         DEBUG(Q_FUNC_INFO << ", WARNING: latex not found!")
3962         return;
3963     }
3964 
3965     QProcess tex;
3966     tex.start(latexFullPath, QStringList() << QStringLiteral("--version"), QProcess::ReadOnly);
3967     tex.waitForFinished(500);
3968     QString texVersionOutput = QLatin1String(tex.readAllStandardOutput());
3969     texVersionOutput = texVersionOutput.split(QLatin1Char('\n'))[0];
3970 
3971     int yearidx = -1;
3972     for (int i = texVersionOutput.size() - 1; i >= 0; --i) {
3973         if (texVersionOutput.at(i) == QLatin1Char('2')) {
3974             yearidx = i;
3975             break;
3976         }
3977     }
3978 
3979     if (texVersionOutput.at(yearidx + 1) == QLatin1Char('/'))
3980         yearidx -= 3;
3981 
3982     bool ok;
3983     texVersionOutput.mid(yearidx, 4).toInt(&ok);
3984     int version = -1;
3985     if (ok)
3986         version = texVersionOutput.mid(yearidx, 4).toInt(&ok);
3987 
3988     if (latexHeaders) {
3989         out << QLatin1String("\\documentclass[11pt,a4paper]{article} \n");
3990         out << QLatin1String("\\usepackage{geometry} \n");
3991         out << QLatin1String("\\usepackage{xcolor,colortbl} \n");
3992         if (version >= 2015)
3993             out << QLatin1String("\\extrafloats{1280} \n");
3994         out << QLatin1String("\\definecolor{HeaderBgColor}{rgb}{0.81,0.81,0.81} \n");
3995         out << QLatin1String("\\geometry{ \n");
3996         out << QLatin1String("a4paper, \n");
3997         out << QLatin1String("total={170mm,257mm}, \n");
3998         out << QLatin1String("left=10mm, \n");
3999         out << QLatin1String("top=10mm } \n");
4000 
4001         out << QLatin1String("\\begin{document} \n");
4002         out << QLatin1String("\\title{LabPlot Spreadsheet Export to \\LaTeX{} } \n");
4003         out << QLatin1String("\\author{LabPlot} \n");
4004         out << QLatin1String("\\date{\\today} \n");
4005     }
4006 
4007     QString endTabularTable(QStringLiteral("\\end{tabular} \n \\end{table} \n"));
4008     QString tableCaption(QStringLiteral("\\caption{") + m_spreadsheet->name() + QStringLiteral("} \n"));
4009     QString beginTable(QStringLiteral("\\begin{table}[ht] \n"));
4010 
4011     int rowCount = 0;
4012     const int maxRows = 45;
4013     bool captionRemoved = false;
4014     if (columnsSeparating) {
4015         QVector<int> emptyRowIndices;
4016         for (int table = 0; table < tablesCount; ++table) {
4017             QStringList textable;
4018             captionRemoved = false;
4019             textable << beginTable;
4020 
4021             if (captions)
4022                 textable << tableCaption;
4023             textable << QLatin1String("\\centering \n");
4024             textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
4025             for (int i = 0; i < columnsPerTable; ++i)
4026                 textable << (gridLines ? QLatin1String(" c |") : QLatin1String(" c "));
4027             textable << QLatin1String("} \n");
4028             if (gridLines)
4029                 textable << QLatin1String("\\hline \n");
4030 
4031             if (exportHeaders) {
4032                 if (latexHeaders)
4033                     textable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
4034                 for (int col = table * columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) {
4035                     textable << toExport.at(col)->name();
4036                     if (col != ((table * columnsPerTable) + columnsPerTable) - 1)
4037                         textable << QLatin1String(" & ");
4038                 }
4039                 textable << QLatin1String("\\\\ \n");
4040                 if (gridLines)
4041                     textable << QLatin1String("\\hline \n");
4042             }
4043             for (const auto& s : textable)
4044                 out << s;
4045 
4046             QStringList values;
4047             for (int row = 0; row < totalRowCount; ++row) {
4048                 values.clear();
4049                 bool notEmpty = false;
4050                 for (int col = table * columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) {
4051                     if (toExport.at(col)->isValid(row)) {
4052                         notEmpty = true;
4053                         values << toExport.at(col)->asStringColumn()->textAt(row);
4054                     } else
4055                         values << QLatin1String("-");
4056                     if (col != ((table * columnsPerTable) + columnsPerTable) - 1)
4057                         values << QLatin1String(" & ");
4058                 }
4059                 if (!notEmpty && skipEmptyRows) {
4060                     if (!emptyRowIndices.contains(row))
4061                         emptyRowIndices << row;
4062                 }
4063                 if (emptyRowIndices.contains(row) && notEmpty)
4064                     emptyRowIndices.remove(emptyRowIndices.indexOf(row));
4065 
4066                 if (notEmpty || !skipEmptyRows) {
4067                     for (const auto& s : values)
4068                         out << s;
4069                     out << QLatin1String("\\\\ \n");
4070                     if (gridLines)
4071                         out << QLatin1String("\\hline \n");
4072                     rowCount++;
4073                     if (rowCount == maxRows) {
4074                         out << endTabularTable;
4075                         out << QLatin1String("\\clearpage \n");
4076 
4077                         if (captions)
4078                             if (!captionRemoved)
4079                                 textable.removeAt(1);
4080                         for (const auto& s : textable)
4081                             out << s;
4082                         rowCount = 0;
4083                         if (!captionRemoved)
4084                             captionRemoved = true;
4085                     }
4086                 }
4087             }
4088             out << endTabularTable;
4089         }
4090 
4091         // new table for the remaining columns
4092         QStringList remainingTable;
4093         remainingTable << beginTable;
4094         if (captions)
4095             remainingTable << tableCaption;
4096         remainingTable << QLatin1String("\\centering \n");
4097         remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
4098         for (int c = 0; c < remainingColumns; ++c)
4099             remainingTable << (gridLines ? QLatin1String(" c |") : QLatin1String(" c "));
4100         remainingTable << QLatin1String("} \n");
4101         if (gridLines)
4102             remainingTable << QLatin1String("\\hline \n");
4103         if (exportHeaders) {
4104             if (latexHeaders)
4105                 remainingTable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
4106             for (int col = 0; col < remainingColumns; ++col) {
4107                 remainingTable << toExport.at(col + (tablesCount * columnsPerTable))->name();
4108                 if (col != remainingColumns - 1)
4109                     remainingTable << QLatin1String(" & ");
4110             }
4111             remainingTable << QLatin1String("\\\\ \n");
4112             if (gridLines)
4113                 remainingTable << QLatin1String("\\hline \n");
4114         }
4115 
4116         for (const auto& s : remainingTable)
4117             out << s;
4118 
4119         QStringList values;
4120         captionRemoved = false;
4121         for (int row = 0; row < totalRowCount; ++row) {
4122             values.clear();
4123             bool notEmpty = false;
4124             for (int col = 0; col < remainingColumns; ++col) {
4125                 if (toExport.at(col + (tablesCount * columnsPerTable))->isValid(row)) {
4126                     notEmpty = true;
4127                     values << toExport.at(col + (tablesCount * columnsPerTable))->asStringColumn()->textAt(row);
4128                 } else
4129                     values << QLatin1String("-");
4130                 if (col != remainingColumns - 1)
4131                     values << QLatin1String(" & ");
4132             }
4133             if (!emptyRowIndices.contains(row) && !notEmpty)
4134                 notEmpty = true;
4135             if (notEmpty || !skipEmptyRows) {
4136                 for (const auto& s : values)
4137                     out << s;
4138                 out << QLatin1String("\\\\ \n");
4139                 if (gridLines)
4140                     out << QLatin1String("\\hline \n");
4141                 rowCount++;
4142                 if (rowCount == maxRows) {
4143                     out << endTabularTable;
4144                     out << QLatin1String("\\pagebreak[4] \n");
4145                     if (captions)
4146                         if (!captionRemoved)
4147                             remainingTable.removeAt(1);
4148                     for (const auto& s : remainingTable)
4149                         out << s;
4150                     rowCount = 0;
4151                     if (!captionRemoved)
4152                         captionRemoved = true;
4153                 }
4154             }
4155         }
4156         out << endTabularTable;
4157     } else {
4158         QStringList textable;
4159         textable << beginTable;
4160         if (captions)
4161             textable << tableCaption;
4162         textable << QLatin1String("\\centering \n");
4163         textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
4164         for (int c = 0; c < cols; ++c)
4165             textable << (gridLines ? QLatin1String(" c |") : QLatin1String(" c "));
4166         textable << QLatin1String("} \n");
4167         if (gridLines)
4168             textable << QLatin1String("\\hline \n");
4169         if (exportHeaders) {
4170             if (latexHeaders)
4171                 textable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
4172             for (int col = 0; col < cols; ++col) {
4173                 textable << toExport.at(col)->name();
4174                 if (col != cols - 1)
4175                     textable << QLatin1String(" & ");
4176             }
4177             textable << QLatin1String("\\\\ \n");
4178             if (gridLines)
4179                 textable << QLatin1String("\\hline \n");
4180         }
4181 
4182         for (const auto& s : textable)
4183             out << s;
4184         QStringList values;
4185         captionRemoved = false;
4186         for (int row = 0; row < totalRowCount; ++row) {
4187             values.clear();
4188             bool notEmpty = false;
4189 
4190             for (int col = 0; col < cols; ++col) {
4191                 if (toExport.at(col)->isValid(row)) {
4192                     notEmpty = true;
4193                     values << toExport.at(col)->asStringColumn()->textAt(row);
4194                 } else
4195                     values << QStringLiteral("-");
4196                 if (col != cols - 1)
4197                     values << QStringLiteral(" & ");
4198             }
4199 
4200             if (notEmpty || !skipEmptyRows) {
4201                 for (const auto& s : values)
4202                     out << s;
4203                 out << QLatin1String("\\\\ \n");
4204                 if (gridLines)
4205                     out << QLatin1String("\\hline \n");
4206                 rowCount++;
4207                 if (rowCount == maxRows) {
4208                     out << endTabularTable;
4209                     out << QLatin1String("\\clearpage \n");
4210                     if (captions)
4211                         if (!captionRemoved)
4212                             textable.removeAt(1);
4213                     for (const auto& s : textable)
4214                         out << s;
4215                     rowCount = 0;
4216                     if (!captionRemoved)
4217                         captionRemoved = true;
4218                 }
4219             }
4220         }
4221         out << endTabularTable;
4222     }
4223     if (latexHeaders)
4224         out << QLatin1String("\\end{document} \n");
4225 
4226     if (!exportEntire) {
4227         qDeleteAll(toExport);
4228         toExport.clear();
4229     } else
4230         toExport.clear();
4231 }
4232 
4233 void SpreadsheetView::exportToFits(const QString& fileName, const int exportTo, const bool commentsAsUnits) const {
4234     auto* filter = new FITSFilter;
4235 
4236     filter->setExportTo(exportTo);
4237     filter->setCommentsAsUnits(commentsAsUnits);
4238     filter->write(fileName, m_spreadsheet);
4239 
4240     delete filter;
4241 }
4242 
4243 void SpreadsheetView::exportToXLSX(const QString& fileName, const bool exportHeader) const {
4244     auto* filter = new XLSXFilter;
4245 
4246     DEBUG("EXPORT HEADER = " << exportHeader)
4247     filter->setColumnNamesAsFirstRow(exportHeader);
4248     filter->write(fileName, m_spreadsheet);
4249 
4250     delete filter;
4251 }
4252 
4253 void SpreadsheetView::exportToSQLite(const QString& path) const {
4254     QFile file(path);
4255     if (!file.open(QFile::WriteOnly | QFile::Truncate))
4256         return;
4257 
4258     PERFTRACE(QStringLiteral("export spreadsheet to SQLite database"));
4259     QApplication::processEvents(QEventLoop::AllEvents, 0);
4260 
4261     // create database
4262     const QStringList& drivers = QSqlDatabase::drivers();
4263     QString driver;
4264     if (drivers.contains(QLatin1String("QSQLITE3")))
4265         driver = QLatin1String("QSQLITE3");
4266     else
4267         driver = QLatin1String("QSQLITE");
4268 
4269     QSqlDatabase db = QSqlDatabase::addDatabase(driver);
4270     db.setDatabaseName(path);
4271     if (!db.open()) {
4272         RESET_CURSOR;
4273         KMessageBox::error(nullptr, i18n("Couldn't create the SQLite database %1.", path));
4274     }
4275 
4276     // create table
4277     const int cols = m_spreadsheet->columnCount();
4278     QString query = QLatin1String("create table ") + m_spreadsheet->name() + QLatin1String(" (");
4279     for (int i = 0; i < cols; ++i) {
4280         Column* col = m_spreadsheet->column(i);
4281         if (i != 0)
4282             query += QLatin1String(", ");
4283 
4284         query += QLatin1String("\"") + col->name() + QLatin1String("\" ");
4285         switch (col->columnMode()) {
4286         case AbstractColumn::ColumnMode::Double:
4287             query += QLatin1String("REAL");
4288             break;
4289         case AbstractColumn::ColumnMode::Integer:
4290         case AbstractColumn::ColumnMode::BigInt:
4291             query += QLatin1String("INTEGER");
4292             break;
4293         case AbstractColumn::ColumnMode::Text:
4294         case AbstractColumn::ColumnMode::Month:
4295         case AbstractColumn::ColumnMode::Day:
4296         case AbstractColumn::ColumnMode::DateTime:
4297             query += QLatin1String("TEXT");
4298             break;
4299         }
4300     }
4301     query += QLatin1Char(')');
4302     QSqlQuery q;
4303     if (!q.exec(query)) {
4304         RESET_CURSOR;
4305         KMessageBox::error(nullptr, i18n("Failed to create table in the SQLite database %1.", path) + QLatin1Char('\n') + q.lastError().databaseText());
4306         db.close();
4307         return;
4308     }
4309 
4310     int maxRow = maxRowToExport();
4311     if (maxRow < 0) {
4312         db.close();
4313         return;
4314     }
4315 
4316     // create bulk insert statement in batches of 10k rows
4317     {
4318         PERFTRACE(QStringLiteral("Insert the data"));
4319         q.exec(QLatin1String("BEGIN TRANSACTION;"));
4320 
4321         // create the first part of the INSERT-statement without the values
4322         QString insertQuery = QStringLiteral("INSERT INTO '") + m_spreadsheet->name() + QStringLiteral("' (");
4323         for (int i = 0; i < cols; ++i) {
4324             if (i != 0)
4325                 insertQuery += QLatin1String(", ");
4326             insertQuery += QLatin1Char('\'') + m_spreadsheet->column(i)->name() + QLatin1Char('\'');
4327         }
4328         insertQuery += QLatin1String(") VALUES ");
4329 
4330         // add values in chunks of 10k row
4331         int chunkSize = 10000;
4332         int chunks = std::ceil((double)maxRow / chunkSize);
4333         for (int chunk = 0; chunk < chunks; ++chunk) {
4334             query = insertQuery;
4335             for (int i = 0; i < chunkSize; ++i) {
4336                 int row = chunk * chunkSize + i;
4337                 if (row > maxRow)
4338                     break;
4339 
4340                 if (i != 0)
4341                     query += QLatin1String(",");
4342 
4343                 query += QLatin1Char('(');
4344                 for (int j = 0; j < cols; ++j) {
4345                     auto* col = m_spreadsheet->column(j);
4346                     if (j != 0)
4347                         query += QLatin1String(", ");
4348 
4349                     query += QLatin1Char('\'') + col->asStringColumn()->textAt(row) + QLatin1Char('\'');
4350                 }
4351                 query += QLatin1String(")");
4352             }
4353             query += QLatin1Char(';');
4354 
4355             // insert values for the current chunk of data
4356             if (!q.exec(query)) {
4357                 RESET_CURSOR;
4358                 KMessageBox::error(nullptr, i18n("Failed to insert values into the table."));
4359                 QDEBUG(Q_FUNC_INFO << ", bulk insert error " << q.lastError().databaseText());
4360                 db.close();
4361                 return;
4362             }
4363         }
4364 
4365     } // end of perf-trace scope
4366 
4367     // commit the transaction and close the database
4368     q.exec(QLatin1String("COMMIT TRANSACTION;"));
4369     db.close();
4370 }