File indexing completed on 2024-05-12 15:27:41

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