"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/commonfrontend/spreadsheet/SpreadsheetView.cpp" (24 Feb 2021, 134201 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "SpreadsheetView.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 /***************************************************************************
    2     File                 : SpreadsheetView.cpp
    3     Project              : LabPlot
    4     Description          : View class for Spreadsheet
    5     --------------------------------------------------------------------
    6     Copyright            : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de)
    7     Copyright            : (C) 2016      by Fabian Kristof (fkristofszabolcs@gmail.com)
    8     Copyright            : (C) 2020 by Stefan Gerlach (stefan.gerlach@uni.kn)
    9 
   10  ***************************************************************************/
   11 
   12 /***************************************************************************
   13  *                                                                         *
   14  *  This program is free software; you can redistribute it and/or modify   *
   15  *  it under the terms of the GNU General Public License as published by   *
   16  *  the Free Software Foundation; either version 2 of the License, or      *
   17  *  (at your option) any later version.                                    *
   18  *                                                                         *
   19  *  This program is distributed in the hope that it will be useful,        *
   20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   22  *  GNU General Public License for more details.                           *
   23  *                                                                         *
   24  *   You should have received a copy of the GNU General Public License     *
   25  *   along with this program; if not, write to the Free Software           *
   26  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   27  *   Boston, MA  02110-1301  USA                                           *
   28  *                                                                         *
   29  ***************************************************************************/
   30 
   31 #include "SpreadsheetView.h"
   32 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
   33 #include "backend/spreadsheet/SpreadsheetModel.h"
   34 #include "backend/spreadsheet/Spreadsheet.h"
   35 #include "commonfrontend/spreadsheet/SpreadsheetItemDelegate.h"
   36 #include "commonfrontend/spreadsheet/SpreadsheetHeaderView.h"
   37 #include "backend/datasources/filters/FITSFilter.h"
   38 #include "backend/lib/macros.h"
   39 #include "backend/lib/trace.h"
   40 #include "backend/core/column/Column.h"
   41 #include "backend/core/column/ColumnPrivate.h"
   42 #include "backend/core/datatypes/SimpleCopyThroughFilter.h"
   43 #include "backend/core/datatypes/Double2StringFilter.h"
   44 #include "backend/core/datatypes/String2DoubleFilter.h"
   45 #include "backend/core/datatypes/DateTime2StringFilter.h"
   46 #include "backend/core/datatypes/String2DateTimeFilter.h"
   47 
   48 #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h"
   49 #include "kdefrontend/spreadsheet/PlotDataDialog.h"
   50 #include "kdefrontend/spreadsheet/AddSubtractValueDialog.h"
   51 #include "kdefrontend/spreadsheet/DropValuesDialog.h"
   52 #include "kdefrontend/spreadsheet/GoToDialog.h"
   53 #include "kdefrontend/spreadsheet/RescaleDialog.h"
   54 #include "kdefrontend/spreadsheet/SortDialog.h"
   55 #include "kdefrontend/spreadsheet/RandomValuesDialog.h"
   56 #include "kdefrontend/spreadsheet/EquidistantValuesDialog.h"
   57 #include "kdefrontend/spreadsheet/FunctionValuesDialog.h"
   58 #include "kdefrontend/spreadsheet/StatisticsDialog.h"
   59 
   60 #ifdef Q_OS_MAC
   61 #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h"
   62 #endif
   63 
   64 #include <KLocalizedString>
   65 #include <KMessageBox>
   66 
   67 #include <QKeyEvent>
   68 #include <QClipboard>
   69 #include <QInputDialog>
   70 #include <QDate>
   71 #include <QApplication>
   72 #include <QMenu>
   73 #include <QMessageBox>
   74 #include <QMimeData>
   75 #include <QPainter>
   76 #include <QPrinter>
   77 #include <QPrintDialog>
   78 #include <QPrintPreviewDialog>
   79 #include <QSqlDatabase>
   80 #include <QSqlError>
   81 #include <QSqlQuery>
   82 #include <QTableView>
   83 #include <QTimer>
   84 #include <QToolBar>
   85 #include <QTextStream>
   86 #include <QProcess>
   87 #include <QRegularExpression>
   88 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
   89 #include <QRandomGenerator>
   90 #endif
   91 
   92 #include <algorithm> //for std::reverse
   93 
   94 extern "C" {
   95 #include <gsl/gsl_math.h>
   96 }
   97 
   98 enum NormalizationMethod {DivideBySum, DivideByMin, DivideByMax, DivideByCount,
   99                         DivideByMean, DivideByMedian, DivideByMode, DivideByRange,
  100                         DivideBySD, DivideByMAD, DivideByIQR,
  101                         ZScoreSD, ZScoreMAD, ZScoreIQR,
  102                         Rescale};
  103 
  104 enum TukeyLadderPower {InverseSquared, Inverse, InverseSquareRoot, Log, SquareRoot, Squared, Cube};
  105 
  106 /*!
  107     \class SpreadsheetView
  108     \brief View class for Spreadsheet
  109 
  110     \ingroup commonfrontend
  111  */
  112 SpreadsheetView::SpreadsheetView(Spreadsheet* spreadsheet, bool readOnly) : QWidget(),
  113     m_tableView(new QTableView(this)),
  114     m_spreadsheet(spreadsheet),
  115     m_model(new SpreadsheetModel(spreadsheet)),
  116     m_readOnly(readOnly) {
  117 
  118     auto* layout = new QHBoxLayout(this);
  119     layout->setContentsMargins(0,0,0,0);
  120     layout->addWidget(m_tableView);
  121     if (m_readOnly)
  122         m_tableView->setEditTriggers(QTableView::NoEditTriggers);
  123     init();
  124 
  125     //resize the view to show alls columns and the first 10 rows.
  126     //no need to resize the view when the project is being opened,
  127     //all views will be resized to the stored values at the end
  128     if (!m_spreadsheet->isLoading()) {
  129         int w = m_tableView->verticalHeader()->width();
  130         int h = m_horizontalHeader->height();
  131         for (int i = 0; i < m_horizontalHeader->count(); ++i)
  132             w += m_horizontalHeader->sectionSize(i);
  133 
  134         if (m_tableView->verticalHeader()->count() <= 10)
  135             h += m_tableView->verticalHeader()->sectionSize(0)*m_tableView->verticalHeader()->count();
  136         else
  137             h += m_tableView->verticalHeader()->sectionSize(0)*11;
  138 
  139         resize(w+50, h);
  140     }
  141 
  142     KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet"));
  143     showComments(group.readEntry(QLatin1String("ShowComments"), false));
  144 }
  145 
  146 SpreadsheetView::~SpreadsheetView() {
  147     delete m_model;
  148 }
  149 
  150 void SpreadsheetView::init() {
  151     initActions();
  152     initMenus();
  153 
  154     m_tableView->setModel(m_model);
  155     auto* delegate = new SpreadsheetItemDelegate(this);
  156     connect(delegate, &SpreadsheetItemDelegate::returnPressed, this, &SpreadsheetView::advanceCell);
  157     connect(delegate, &SpreadsheetItemDelegate::editorEntered, this, [=]() {
  158 //      action_insert_row_below->setShortcut(QKeySequence());
  159         m_editorEntered = true;
  160     });
  161     connect(delegate, &SpreadsheetItemDelegate::closeEditor, this, [=]() {
  162 //      action_insert_row_below->setShortcut(Qt::Key_Insert);
  163         m_editorEntered = false;
  164     });
  165 
  166     m_tableView->setItemDelegate(delegate);
  167     m_tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
  168 
  169     //horizontal header
  170     m_horizontalHeader = new SpreadsheetHeaderView(this);
  171     m_horizontalHeader->setSectionsClickable(true);
  172     m_horizontalHeader->setHighlightSections(true);
  173     m_tableView->setHorizontalHeader(m_horizontalHeader);
  174     m_horizontalHeader->setSectionsMovable(true);
  175     m_horizontalHeader->installEventFilter(this);
  176 
  177     resizeHeader();
  178 
  179     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionMoved, this, &SpreadsheetView::handleHorizontalSectionMoved);
  180     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionDoubleClicked, this, &SpreadsheetView::handleHorizontalHeaderDoubleClicked);
  181     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionResized, this, &SpreadsheetView::handleHorizontalSectionResized);
  182     connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionClicked, this, &SpreadsheetView::columnClicked);
  183 
  184     // vertical header
  185     QHeaderView* v_header = m_tableView->verticalHeader();
  186     v_header->setSectionResizeMode(QHeaderView::Fixed);
  187     v_header->setSectionsMovable(false);
  188     v_header->installEventFilter(this);
  189 
  190     setFocusPolicy(Qt::StrongFocus);
  191     setFocus();
  192     installEventFilter(this);
  193     connectActions();
  194     showComments(false);
  195 
  196     connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::updateHeaderGeometry);
  197     connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::handleHeaderDataChanged);
  198     connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetView::handleAspectAdded);
  199     connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved,this, &SpreadsheetView::handleAspectAboutToBeRemoved);
  200     connect(m_spreadsheet, &Spreadsheet::requestProjectContextMenu, this, &SpreadsheetView::createContextMenu);
  201 
  202     for (auto* column : m_spreadsheet->children<Column>())
  203         connect(column, &Column::requestProjectContextMenu, this, &SpreadsheetView::createColumnContextMenu);
  204 
  205     //selection relevant connections
  206     QItemSelectionModel* sel_model = m_tableView->selectionModel();
  207     connect(sel_model, &QItemSelectionModel::currentColumnChanged, this, &SpreadsheetView::currentColumnChanged);
  208     connect(sel_model, &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged);
  209     connect(sel_model, &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged);
  210 
  211     connect(m_spreadsheet, &Spreadsheet::columnSelected, this, &SpreadsheetView::selectColumn);
  212     connect(m_spreadsheet, &Spreadsheet::columnDeselected, this, &SpreadsheetView::deselectColumn);
  213 }
  214 
  215 /*!
  216     set the column sizes to the saved values or resize to content if no size was saved yet
  217 */
  218 void SpreadsheetView::resizeHeader() {
  219     const auto columns = m_spreadsheet->children<Column>();
  220     int i = 0;
  221     for (auto col: columns) {
  222         if (col->width() == 0)
  223             m_tableView->resizeColumnToContents(i);
  224         else
  225             m_tableView->setColumnWidth(i, col->width());
  226         i++;
  227     }
  228 }
  229 
  230 void SpreadsheetView::initActions() {
  231     // selection related actions
  232     action_cut_selection = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cu&t"), this);
  233     action_copy_selection = new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy"), this);
  234     action_paste_into_selection = new QAction(QIcon::fromTheme("edit-paste"), i18n("Past&e"), this);
  235     action_mask_selection = new QAction(QIcon::fromTheme("edit-node"), i18n("&Mask"), this);
  236     action_unmask_selection = new QAction(QIcon::fromTheme("format-remove-node"), i18n("&Unmask"), this);
  237     action_clear_selection = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Content"), this);
  238     action_select_all = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this);
  239 
  240 //  action_set_formula = new QAction(QIcon::fromTheme(QString()), i18n("Assign &Formula"), this);
  241 //  action_recalculate = new QAction(QIcon::fromTheme(QString()), i18n("Recalculate"), this);
  242 //  action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this);
  243     action_fill_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this);
  244     action_fill_random = new QAction(QIcon::fromTheme(QString()), i18n("Uniform Random Values"), this);
  245     action_fill_random_nonuniform = new QAction(QIcon::fromTheme(QString()), i18n("Random Values"), this);
  246     action_fill_equidistant = new QAction(QIcon::fromTheme(QString()), i18n("Equidistant Values"), this);
  247     action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this);
  248     action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this);
  249 
  250     //spreadsheet related actions
  251     action_toggle_comments = new QAction(QIcon::fromTheme("document-properties"), i18n("Show Comments"), this);
  252     action_clear_spreadsheet = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Spreadsheet"), this);
  253     action_clear_masks = new QAction(QIcon::fromTheme("format-remove-node"), i18n("Clear Masks"), this);
  254     action_sort_spreadsheet = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Sort Spreadsheet"), this);
  255     action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this);
  256     action_statistics_all_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this );
  257 
  258     // column related actions
  259     action_insert_column_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Column Left"), this);
  260     action_insert_column_right = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("Insert Column Right"), this);
  261     action_insert_columns_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Multiple Columns Left"), this);
  262     action_insert_columns_right = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("Insert Multiple Columns Right"), this);
  263     action_remove_columns = new QAction(QIcon::fromTheme("edit-table-delete-column"), i18n("Remove Selected Columns"), this);
  264     action_clear_columns = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Selected Columns"), this);
  265 
  266     action_set_as_none = new QAction(i18n("None"), this);
  267     action_set_as_none->setData(static_cast<int>(AbstractColumn::PlotDesignation::NoDesignation));
  268 
  269     action_set_as_x = new QAction(QLatin1String("X"), this);
  270     action_set_as_x->setData(static_cast<int>(AbstractColumn::PlotDesignation::X));
  271 
  272     action_set_as_y = new QAction(QLatin1String("Y"), this);
  273     action_set_as_y->setData(static_cast<int>(AbstractColumn::PlotDesignation::Y));
  274 
  275     action_set_as_z = new QAction(QLatin1String("Z"), this);
  276     action_set_as_z->setData(static_cast<int>(AbstractColumn::PlotDesignation::Z));
  277 
  278     action_set_as_xerr = new QAction(i18n("X-error"), this);
  279     action_set_as_xerr->setData(static_cast<int>(AbstractColumn::PlotDesignation::XError));
  280 
  281     action_set_as_xerr_minus = new QAction(i18n("X-error minus"), this);
  282     action_set_as_xerr_minus->setData(static_cast<int>(AbstractColumn::PlotDesignation::XErrorMinus));
  283 
  284     action_set_as_xerr_plus = new QAction(i18n("X-error plus"), this);
  285     action_set_as_xerr_plus->setData(static_cast<int>(AbstractColumn::PlotDesignation::XErrorPlus));
  286 
  287     action_set_as_yerr = new QAction(i18n("Y-error"), this);
  288     action_set_as_yerr->setData(static_cast<int>(AbstractColumn::PlotDesignation::YError));
  289 
  290     action_set_as_yerr_minus = new QAction(i18n("Y-error minus"), this);
  291     action_set_as_yerr_minus->setData(static_cast<int>(AbstractColumn::PlotDesignation::YErrorMinus));
  292 
  293     action_set_as_yerr_plus = new QAction(i18n("Y-error plus"), this);
  294     action_set_as_yerr_plus->setData(static_cast<int>(AbstractColumn::PlotDesignation::YErrorPlus));
  295 
  296     //data manipulation
  297     action_add_value = new QAction(i18n("Add Value"), this);
  298     action_add_value->setData(AddSubtractValueDialog::Add);
  299     action_subtract_value = new QAction(i18n("Subtract Value"), this);
  300     action_subtract_value->setData(AddSubtractValueDialog::Subtract);
  301     action_multiply_value = new QAction(i18n("Multiply by Value"), this);
  302     action_multiply_value->setData(AddSubtractValueDialog::Multiply);
  303     action_divide_value = new QAction(i18n("Divide by Value"), this);
  304     action_divide_value->setData(AddSubtractValueDialog::Divide);
  305     action_drop_values = new QAction(QIcon::fromTheme(QString()), i18n("Drop Values"), this);
  306     action_mask_values = new QAction(QIcon::fromTheme(QString()), i18n("Mask Values"), this);
  307     action_reverse_columns = new QAction(QIcon::fromTheme(QString()), i18n("Reverse"), this);
  308 //  action_join_columns = new QAction(QIcon::fromTheme(QString()), i18n("Join"), this);
  309 
  310     //normalization
  311     normalizeColumnActionGroup = new QActionGroup(this);
  312     QAction* normalizeAction = new QAction(i18n("Divide by Sum"), normalizeColumnActionGroup);
  313     normalizeAction->setData(DivideBySum);
  314 
  315     normalizeAction = new QAction(i18n("Divide by Min"), normalizeColumnActionGroup);
  316     normalizeAction->setData(DivideByMin);
  317 
  318     normalizeAction = new QAction(i18n("Divide by Max"), normalizeColumnActionGroup);
  319     normalizeAction->setData(DivideByMax);
  320 
  321     normalizeAction = new QAction(i18n("Divide by Count"), normalizeColumnActionGroup);
  322     normalizeAction->setData(DivideByCount);
  323 
  324     normalizeAction = new QAction(i18n("Divide by Mean"), normalizeColumnActionGroup);
  325     normalizeAction->setData(DivideByMean);
  326 
  327     normalizeAction = new QAction(i18n("Divide by Median"), normalizeColumnActionGroup);
  328     normalizeAction->setData(DivideByMedian);
  329 
  330     normalizeAction = new QAction(i18n("Divide by Mode"), normalizeColumnActionGroup);
  331     normalizeAction->setData(DivideByMode);
  332 
  333     normalizeAction = new QAction(i18n("Divide by Range"), normalizeColumnActionGroup);
  334     normalizeAction->setData(DivideByRange);
  335 
  336     normalizeAction = new QAction(i18n("Divide by SD"), normalizeColumnActionGroup);
  337     normalizeAction->setData(DivideBySD);
  338 
  339     normalizeAction = new QAction(i18n("Divide by MAD"), normalizeColumnActionGroup);
  340     normalizeAction->setData(DivideByMAD);
  341 
  342     normalizeAction = new QAction(i18n("Divide by IQR"), normalizeColumnActionGroup);
  343     normalizeAction->setData(DivideByIQR);
  344 
  345     normalizeAction = new QAction(QLatin1String("(x-Mean)/SD"), normalizeColumnActionGroup);
  346     normalizeAction->setData(ZScoreSD);
  347 
  348     normalizeAction = new QAction(QLatin1String("(x-Median)/MAD"), normalizeColumnActionGroup);
  349     normalizeAction->setData(ZScoreMAD);
  350 
  351     normalizeAction = new QAction(QLatin1String("(x-Median)/IQR"), normalizeColumnActionGroup);
  352     normalizeAction->setData(ZScoreIQR);
  353 
  354     normalizeAction = new QAction(QLatin1String("Rescale to [a, b]"), normalizeColumnActionGroup);
  355     normalizeAction->setData(Rescale);
  356 
  357 //  action_normalize_selection = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize Selection"), this);
  358 
  359     //Tukey's ladder of powers
  360     ladderOfPowersActionGroup = new QActionGroup(this);
  361 
  362     QAction* ladderAction = new QAction("x³", ladderOfPowersActionGroup);
  363     ladderAction->setData(Cube);
  364 
  365     ladderAction = new QAction("x²", ladderOfPowersActionGroup);
  366     ladderAction->setData(Squared);
  367 
  368     ladderAction = new QAction("√x", ladderOfPowersActionGroup);
  369     ladderAction->setData(SquareRoot);
  370 
  371     ladderAction = new QAction(QLatin1String("log(x)"), ladderOfPowersActionGroup);
  372     ladderAction->setData(Log);
  373 
  374     ladderAction = new QAction("1/√x", ladderOfPowersActionGroup);
  375     ladderAction->setData(InverseSquareRoot);
  376 
  377     ladderAction = new QAction(QLatin1String("1/x"), ladderOfPowersActionGroup);
  378     ladderAction->setData(Inverse);
  379 
  380     ladderAction = new QAction("1/x²", ladderOfPowersActionGroup);
  381     ladderAction->setData(InverseSquared);
  382 
  383     //sort and statistics
  384     action_sort_columns = new QAction(QIcon::fromTheme(QString()), i18n("&Selected Columns"), this);
  385     action_sort_asc_column = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Ascending"), this);
  386     action_sort_desc_column = new QAction(QIcon::fromTheme("view-sort-descending"), i18n("&Descending"), this);
  387     action_statistics_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Column Statisti&cs"), this);
  388 
  389     // row related actions
  390     action_insert_row_above = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("Insert Row Above"), this);
  391     action_insert_row_below = new QAction(QIcon::fromTheme("edit-table-insert-row-below"), i18n("Insert Row Below"), this);
  392     //TODO: setting of the following shortcut collides with the key press handling in the event filter
  393     //action_insert_row_below->setShortcut(Qt::Key_Insert);
  394     action_insert_rows_above = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("Insert Multiple Rows Above"), this);
  395     action_insert_rows_below = new QAction(QIcon::fromTheme("edit-table-insert-row-below"), i18n("Insert Multiple Rows Below"), this);
  396     action_remove_rows = new QAction(QIcon::fromTheme("edit-table-delete-row"), i18n("Remo&ve Selected Rows"), this);
  397     action_clear_rows = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selected Rows"), this);
  398     action_statistics_rows = new QAction(QIcon::fromTheme("view-statistics"), i18n("Row Statisti&cs"), this);
  399 
  400     //plot data action
  401     action_plot_data_xycurve = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-Curve"), this);
  402     action_plot_data_xycurve->setData(static_cast<int>(PlotDataDialog::PlotType::XYCurve));
  403     action_plot_data_histogram = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this);
  404     action_plot_data_histogram->setData(static_cast<int>(PlotDataDialog::PlotType::Histogram));
  405 
  406     //Analyze and plot menu actions
  407     addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Reduce Data"), this);
  408 //  addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-data-reduction-curve"), i18n("Reduce Data"), this);
  409     addDataReductionAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::DataReduction));
  410     addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this);
  411 //  addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-differentiation-curve"), i18n("Differentiate"), this);
  412     addDifferentiationAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::Differentiation));
  413     addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this);
  414 //  addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("Integrate"), this);
  415     addIntegrationAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::Integration));
  416     addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this);
  417     addInterpolationAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::Interpolation));
  418     addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this);
  419     addSmoothAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::Smoothing));
  420 
  421     QAction* fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Linear"), this);
  422     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitLinear));
  423     addFitAction.append(fitAction);
  424 
  425     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Power"), this);
  426     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitPower));
  427     addFitAction.append(fitAction);
  428 
  429     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Exponential (degree 1)"), this);
  430     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitExp1));
  431     addFitAction.append(fitAction);
  432 
  433     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Exponential (degree 2)"), this);
  434     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitExp2));
  435     addFitAction.append(fitAction);
  436 
  437     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Inverse Exponential"), this);
  438     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitInvExp));
  439     addFitAction.append(fitAction);
  440 
  441     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Gauss"), this);
  442     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitGauss));
  443     addFitAction.append(fitAction);
  444 
  445     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Cauchy-Lorentz"), this);
  446     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitCauchyLorentz));
  447     addFitAction.append(fitAction);
  448 
  449     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Arc Tangent"), this);
  450     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitTan));
  451     addFitAction.append(fitAction);
  452 
  453     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Hyperbolic Tangent"), this);
  454     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitTanh));
  455     addFitAction.append(fitAction);
  456 
  457     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Error Function"), this);
  458     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitErrFunc));
  459     addFitAction.append(fitAction);
  460 
  461     fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Custom"), this);
  462     fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitCustom));
  463     addFitAction.append(fitAction);
  464 
  465     addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this);
  466     addFourierFilterAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FourierFilter));
  467 }
  468 
  469 void SpreadsheetView::initMenus() {
  470     //Selection menu
  471     m_selectionMenu = new QMenu(i18n("Selection"), this);
  472     m_selectionMenu->setIcon(QIcon::fromTheme("selection"));
  473 
  474     if (!m_readOnly) {
  475 //      submenu = new QMenu(i18n("Fi&ll Selection With"), this);
  476 //      submenu->setIcon(QIcon::fromTheme("select-rectangle"));
  477 //      submenu->addAction(action_fill_sel_row_numbers);
  478 //      submenu->addAction(action_fill_const);
  479 //      m_selectionMenu->addMenu(submenu);
  480 //      m_selectionMenu->addSeparator();
  481         m_selectionMenu->addAction(action_cut_selection);
  482     }
  483 
  484     m_selectionMenu->addAction(action_copy_selection);
  485 
  486     if (!m_readOnly) {
  487         m_selectionMenu->addAction(action_paste_into_selection);
  488         m_selectionMenu->addAction(action_clear_selection);
  489         m_selectionMenu->addSeparator();
  490         m_selectionMenu->addAction(action_mask_selection);
  491         m_selectionMenu->addAction(action_unmask_selection);
  492         m_selectionMenu->addSeparator();
  493 //      m_selectionMenu->addAction(action_normalize_selection);
  494     }
  495 
  496     //plot data menu
  497     m_plotDataMenu = new QMenu(i18n("Plot Data"), this);
  498     m_plotDataMenu->addAction(action_plot_data_xycurve);
  499     m_plotDataMenu->addAction(action_plot_data_histogram);
  500 
  501     // Column menu
  502     m_columnMenu = new QMenu(this);
  503     m_columnMenu->addMenu(m_plotDataMenu);
  504 
  505     // Data fit sub-menu
  506     QMenu* dataFitMenu = new QMenu(i18n("Fit"), this);
  507     dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve"));
  508     dataFitMenu->addAction(addFitAction.at(0));
  509     dataFitMenu->addAction(addFitAction.at(1));
  510     dataFitMenu->addAction(addFitAction.at(2));
  511     dataFitMenu->addAction(addFitAction.at(3));
  512     dataFitMenu->addAction(addFitAction.at(4));
  513     dataFitMenu->addSeparator();
  514     dataFitMenu->addAction(addFitAction.at(5));
  515     dataFitMenu->addAction(addFitAction.at(6));
  516     dataFitMenu->addSeparator();
  517     dataFitMenu->addAction(addFitAction.at(7));
  518     dataFitMenu->addAction(addFitAction.at(8));
  519     dataFitMenu->addAction(addFitAction.at(9));
  520     dataFitMenu->addSeparator();
  521     dataFitMenu->addAction(addFitAction.at(10));
  522 
  523     //analyze and plot data menu
  524     m_analyzePlotMenu = new QMenu(i18n("Analyze and Plot Data"), this);
  525     m_analyzePlotMenu->addMenu(dataFitMenu);
  526     m_analyzePlotMenu->addSeparator();
  527     m_analyzePlotMenu->addAction(addDifferentiationAction);
  528     m_analyzePlotMenu->addAction(addIntegrationAction);
  529     m_analyzePlotMenu->addSeparator();
  530     m_analyzePlotMenu->addAction(addInterpolationAction);
  531     m_analyzePlotMenu->addAction(addSmoothAction);
  532     m_analyzePlotMenu->addSeparator();
  533     m_analyzePlotMenu->addAction(addFourierFilterAction);
  534     m_analyzePlotMenu->addSeparator();
  535     m_analyzePlotMenu->addAction(addDataReductionAction);
  536     m_columnMenu->addMenu(m_analyzePlotMenu);
  537 
  538     m_columnSetAsMenu = new QMenu(i18n("Set Column As"), this);
  539     m_columnMenu->addSeparator();
  540     m_columnSetAsMenu->addAction(action_set_as_x);
  541     m_columnSetAsMenu->addAction(action_set_as_y);
  542     m_columnSetAsMenu->addAction(action_set_as_z);
  543     m_columnSetAsMenu->addSeparator();
  544     m_columnSetAsMenu->addAction(action_set_as_xerr);
  545     m_columnSetAsMenu->addAction(action_set_as_xerr_minus);
  546     m_columnSetAsMenu->addAction(action_set_as_xerr_plus);
  547     m_columnSetAsMenu->addSeparator();
  548     m_columnSetAsMenu->addAction(action_set_as_yerr);
  549     m_columnSetAsMenu->addAction(action_set_as_yerr_minus);
  550     m_columnSetAsMenu->addAction(action_set_as_yerr_plus);
  551     m_columnSetAsMenu->addSeparator();
  552     m_columnSetAsMenu->addAction(action_set_as_none);
  553     m_columnMenu->addMenu(m_columnSetAsMenu);
  554 
  555     if (!m_readOnly) {
  556         m_columnGenerateDataMenu = new QMenu(i18n("Generate Data"), this);
  557         m_columnGenerateDataMenu->addAction(action_fill_row_numbers);
  558         m_columnGenerateDataMenu->addAction(action_fill_const);
  559         m_columnGenerateDataMenu->addAction(action_fill_equidistant);
  560         m_columnGenerateDataMenu->addAction(action_fill_random_nonuniform);
  561         m_columnGenerateDataMenu->addAction(action_fill_function);
  562         m_columnMenu->addSeparator();
  563         m_columnMenu->addMenu(m_columnGenerateDataMenu);
  564         m_columnMenu->addSeparator();
  565 
  566         m_columnManipulateDataMenu = new QMenu(i18n("Manipulate Data"), this);
  567         m_columnManipulateDataMenu->addAction(action_add_value);
  568         m_columnManipulateDataMenu->addAction(action_subtract_value);
  569         m_columnManipulateDataMenu->addAction(action_multiply_value);
  570         m_columnManipulateDataMenu->addAction(action_divide_value);
  571         m_columnManipulateDataMenu->addSeparator();
  572         m_columnManipulateDataMenu->addAction(action_reverse_columns);
  573         m_columnManipulateDataMenu->addSeparator();
  574         m_columnManipulateDataMenu->addAction(action_drop_values);
  575         m_columnManipulateDataMenu->addAction(action_mask_values);
  576         m_columnManipulateDataMenu->addSeparator();
  577         //  m_columnManipulateDataMenu->addAction(action_join_columns);
  578 
  579         //normalization menu with the following structure
  580         //Divide by Sum
  581         //Divide by Min
  582         //Divide by Max
  583         //Divide by Count
  584         //--------------
  585         //Divide by Mean
  586         //Divide by Median
  587         //Divide by Mode
  588         //---------------
  589         //Divide by Range
  590         //Divide by SD
  591         //Divide by MAD
  592         //Divide by IQR
  593         //--------------
  594         //(x-Mean)/SD
  595         //(x-Median)/MAD
  596         //(x-Median)/IQR
  597         //--------------
  598         //Rescale to [a, b]
  599 
  600         m_columnNormalizeMenu = new QMenu(i18n("Normalize"), this);
  601         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(0));
  602         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(1));
  603         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(2));
  604         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(3));
  605         m_columnNormalizeMenu->addSeparator();
  606         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(4));
  607         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(5));
  608         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(6));
  609         m_columnNormalizeMenu->addSeparator();
  610         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(7));
  611         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(8));
  612         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(9));
  613         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(10));
  614         m_columnNormalizeMenu->addSeparator();
  615         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(11));
  616         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(12));
  617         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(13));
  618         m_columnNormalizeMenu->addSeparator();
  619         m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(14));
  620         m_columnManipulateDataMenu->addMenu(m_columnNormalizeMenu);
  621 
  622         //"Ladder of powers" transformation
  623         m_columnLadderOfPowersMenu = new QMenu(i18n("Ladder of Powers"), this);
  624         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(0));
  625         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(1));
  626         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(2));
  627         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(3));
  628         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(4));
  629         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(5));
  630         m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(6));
  631 
  632         m_columnManipulateDataMenu->addSeparator();
  633         m_columnManipulateDataMenu->addMenu(m_columnLadderOfPowersMenu);
  634 
  635         m_columnMenu->addMenu(m_columnManipulateDataMenu);
  636         m_columnMenu->addSeparator();
  637 
  638         m_columnSortMenu = new QMenu(i18n("Sort"), this);
  639         m_columnSortMenu->setIcon(QIcon::fromTheme("view-sort-ascending"));
  640         m_columnSortMenu->addAction(action_sort_asc_column);
  641         m_columnSortMenu->addAction(action_sort_desc_column);
  642         m_columnSortMenu->addAction(action_sort_columns);
  643         m_columnMenu->addSeparator();
  644         m_columnMenu->addMenu(m_columnSortMenu);
  645         m_columnMenu->addSeparator();
  646 
  647         m_columnMenu->addAction(action_insert_column_left);
  648         m_columnMenu->addAction(action_insert_column_right);
  649         m_columnMenu->addSeparator();
  650         m_columnMenu->addAction(action_insert_columns_left);
  651         m_columnMenu->addAction(action_insert_columns_right);
  652         m_columnMenu->addSeparator();
  653         m_columnMenu->addAction(action_remove_columns);
  654         m_columnMenu->addAction(action_clear_columns);
  655     }
  656     m_columnMenu->addSeparator();
  657     m_columnMenu->addAction(action_toggle_comments);
  658     m_columnMenu->addSeparator();
  659 
  660     m_columnMenu->addAction(action_statistics_columns);
  661 
  662     //Spreadsheet menu
  663     m_spreadsheetMenu = new QMenu(this);
  664     m_spreadsheetMenu->addMenu(m_plotDataMenu);
  665     m_spreadsheetMenu->addMenu(m_analyzePlotMenu);
  666     m_spreadsheetMenu->addSeparator();
  667     m_spreadsheetMenu->addMenu(m_selectionMenu);
  668     m_spreadsheetMenu->addSeparator();
  669     m_spreadsheetMenu->addAction(action_select_all);
  670     if (!m_readOnly) {
  671         m_spreadsheetMenu->addAction(action_clear_spreadsheet);
  672         m_spreadsheetMenu->addAction(action_clear_masks);
  673         m_spreadsheetMenu->addAction(action_sort_spreadsheet);
  674     }
  675     m_spreadsheetMenu->addSeparator();
  676     m_spreadsheetMenu->addAction(action_go_to_cell);
  677     m_spreadsheetMenu->addSeparator();
  678     m_spreadsheetMenu->addAction(action_toggle_comments);
  679     m_spreadsheetMenu->addSeparator();
  680     m_spreadsheetMenu->addAction(action_statistics_all_columns);
  681 
  682     //Row menu
  683     m_rowMenu = new QMenu(this);
  684     if (!m_readOnly) {
  685 //      submenu = new QMenu(i18n("Fi&ll Selection With"), this);
  686 //      submenu->addAction(action_fill_sel_row_numbers);
  687 //      submenu->addAction(action_fill_const);
  688 //      m_rowMenu->addMenu(submenu);
  689 //      m_rowMenu->addSeparator();
  690 
  691         m_rowMenu->addAction(action_insert_row_above);
  692         m_rowMenu->addAction(action_insert_row_below);
  693         m_rowMenu->addSeparator();
  694 
  695         m_rowMenu->addAction(action_insert_rows_above);
  696         m_rowMenu->addAction(action_insert_rows_below);
  697         m_rowMenu->addSeparator();
  698 
  699         m_rowMenu->addAction(action_remove_rows);
  700         m_rowMenu->addAction(action_clear_rows);
  701     }
  702     m_rowMenu->addSeparator();
  703     m_rowMenu->addAction(action_statistics_rows);
  704     action_statistics_rows->setVisible(false);
  705 }
  706 
  707 void SpreadsheetView::connectActions() {
  708     connect(action_cut_selection, &QAction::triggered, this, &SpreadsheetView::cutSelection);
  709     connect(action_copy_selection, &QAction::triggered, this, &SpreadsheetView::copySelection);
  710     connect(action_paste_into_selection, &QAction::triggered, this, &SpreadsheetView::pasteIntoSelection);
  711     connect(action_mask_selection, &QAction::triggered, this, &SpreadsheetView::maskSelection);
  712     connect(action_unmask_selection, &QAction::triggered, this, &SpreadsheetView::unmaskSelection);
  713 
  714     connect(action_clear_selection, &QAction::triggered, this, &SpreadsheetView::clearSelectedCells);
  715 //  connect(action_recalculate, &QAction::triggered, this, &SpreadsheetView::recalculateSelectedCells);
  716     connect(action_fill_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillWithRowNumbers);
  717 //  connect(action_fill_sel_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRowNumbers);
  718 //  connect(action_fill_random, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRandomNumbers);
  719     connect(action_fill_random_nonuniform, &QAction::triggered, this, &SpreadsheetView::fillWithRandomValues);
  720     connect(action_fill_equidistant, &QAction::triggered, this, &SpreadsheetView::fillWithEquidistantValues);
  721     connect(action_fill_function, &QAction::triggered, this, &SpreadsheetView::fillWithFunctionValues);
  722     connect(action_fill_const, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithConstValues);
  723     connect(action_select_all, &QAction::triggered, m_tableView, &QTableView::selectAll);
  724     connect(action_clear_spreadsheet, &QAction::triggered, m_spreadsheet, &Spreadsheet::clear);
  725     connect(action_clear_masks, &QAction::triggered, m_spreadsheet, &Spreadsheet::clearMasks);
  726     connect(action_sort_spreadsheet, &QAction::triggered, this, &SpreadsheetView::sortSpreadsheet);
  727     connect(action_go_to_cell, &QAction::triggered, this,
  728             static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::goToCell));
  729 
  730     connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft);
  731     connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight);
  732     connect(action_insert_columns_left, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertColumnsLeft));
  733     connect(action_insert_columns_right, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertColumnsRight));
  734     connect(action_remove_columns, &QAction::triggered, this, &SpreadsheetView::removeSelectedColumns);
  735     connect(action_clear_columns, &QAction::triggered, this, &SpreadsheetView::clearSelectedColumns);
  736     connect(action_set_as_none, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  737     connect(action_set_as_x, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  738     connect(action_set_as_y, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  739     connect(action_set_as_z, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  740     connect(action_set_as_xerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  741     connect(action_set_as_xerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  742     connect(action_set_as_xerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  743     connect(action_set_as_yerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  744     connect(action_set_as_yerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  745     connect(action_set_as_yerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs);
  746 
  747     //data manipulation
  748     connect(action_add_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
  749     connect(action_subtract_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
  750     connect(action_multiply_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
  751     connect(action_divide_value, &QAction::triggered, this, &SpreadsheetView::modifyValues);
  752     connect(action_reverse_columns, &QAction::triggered, this, &SpreadsheetView::reverseColumns);
  753     connect(action_drop_values, &QAction::triggered, this, &SpreadsheetView::dropColumnValues);
  754     connect(action_mask_values, &QAction::triggered, this, &SpreadsheetView::maskColumnValues);
  755 //  connect(action_join_columns, &QAction::triggered, this, &SpreadsheetView::joinColumns);
  756     connect(normalizeColumnActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::normalizeSelectedColumns);
  757     connect(ladderOfPowersActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::powerTransformSelectedColumns);
  758 //  connect(action_normalize_selection, &QAction::triggered, this, &SpreadsheetView::normalizeSelection);
  759 
  760     //sort
  761     connect(action_sort_columns, &QAction::triggered, this, &SpreadsheetView::sortSelectedColumns);
  762     connect(action_sort_asc_column, &QAction::triggered, this, &SpreadsheetView::sortColumnAscending);
  763     connect(action_sort_desc_column, &QAction::triggered, this, &SpreadsheetView::sortColumnDescending);
  764 
  765     //statistics
  766     connect(action_statistics_columns, &QAction::triggered, this, &SpreadsheetView::showColumnStatistics);
  767     connect(action_statistics_all_columns, &QAction::triggered, this, &SpreadsheetView::showAllColumnsStatistics);
  768 
  769     connect(action_insert_row_above, &QAction::triggered, this, &SpreadsheetView::insertRowAbove);
  770     connect(action_insert_row_below, &QAction::triggered, this, &SpreadsheetView::insertRowBelow);
  771     connect(action_insert_rows_above, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertRowsAbove));
  772     connect(action_insert_rows_below, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertRowsBelow));
  773     connect(action_remove_rows, &QAction::triggered, this, &SpreadsheetView::removeSelectedRows);
  774     connect(action_clear_rows, &QAction::triggered, this, &SpreadsheetView::clearSelectedRows);
  775     connect(action_statistics_rows, &QAction::triggered, this, &SpreadsheetView::showRowStatistics);
  776     connect(action_toggle_comments, &QAction::triggered, this, &SpreadsheetView::toggleComments);
  777 
  778     connect(action_plot_data_xycurve, &QAction::triggered, this, &SpreadsheetView::plotData);
  779     connect(action_plot_data_histogram, &QAction::triggered, this, &SpreadsheetView::plotData);
  780     connect(addDataReductionAction, &QAction::triggered, this, &SpreadsheetView::plotData);
  781     connect(addDifferentiationAction, &QAction::triggered, this, &SpreadsheetView::plotData);
  782     connect(addIntegrationAction, &QAction::triggered, this, &SpreadsheetView::plotData);
  783     connect(addInterpolationAction, &QAction::triggered, this, &SpreadsheetView::plotData);
  784     connect(addSmoothAction, &QAction::triggered, this, &SpreadsheetView::plotData);
  785     for (const auto& action : addFitAction)
  786         connect(action, &QAction::triggered, this, &SpreadsheetView::plotData);
  787     connect(addFourierFilterAction, &QAction::triggered,this, &SpreadsheetView::plotData);
  788 }
  789 
  790 void SpreadsheetView::fillToolBar(QToolBar* toolBar) {
  791     if (!m_readOnly) {
  792         toolBar->addAction(action_insert_row_above);
  793         toolBar->addAction(action_insert_row_below);
  794         toolBar->addAction(action_remove_rows);
  795     }
  796     toolBar->addAction(action_statistics_rows);
  797     toolBar->addSeparator();
  798     if (!m_readOnly) {
  799         toolBar->addAction(action_insert_column_left);
  800         toolBar->addAction(action_insert_column_right);
  801         toolBar->addAction(action_remove_columns);
  802     }
  803 
  804     toolBar->addAction(action_statistics_columns);
  805     if (!m_readOnly) {
  806         toolBar->addSeparator();
  807         toolBar->addAction(action_sort_asc_column);
  808         toolBar->addAction(action_sort_desc_column);
  809     }
  810 }
  811 
  812 #ifdef Q_OS_MAC
  813 void SpreadsheetView::fillTouchBar(KDMacTouchBar* touchBar){
  814     //touchBar->addAction(action_insert_column_right);
  815 }
  816 #endif
  817 
  818 /*!
  819  * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions.
  820  * The menu is used
  821  *   - as the context menu in SpreadsheetView
  822  *   - as the "spreadsheet menu" in the main menu-bar (called form MainWin)
  823  *   - as a part of the spreadsheet context menu in project explorer
  824  */
  825 void SpreadsheetView::createContextMenu(QMenu* menu) {
  826     Q_ASSERT(menu);
  827 
  828     checkSpreadsheetMenu();
  829 
  830     QAction* firstAction = nullptr;
  831     // if we're populating the context menu for the project explorer, then
  832     //there're already actions available there. Skip the first title-action
  833     //and insert the action at the beginning of the menu.
  834     if (menu->actions().size()>1)
  835         firstAction = menu->actions().at(1);
  836 
  837     if (m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0) {
  838         menu->insertMenu(firstAction, m_plotDataMenu);
  839         menu->insertSeparator(firstAction);
  840     }
  841     menu->insertMenu(firstAction, m_selectionMenu);
  842     menu->insertSeparator(firstAction);
  843     menu->insertAction(firstAction, action_select_all);
  844     if (!m_readOnly) {
  845         menu->insertAction(firstAction, action_clear_spreadsheet);
  846         menu->insertAction(firstAction, action_clear_masks);
  847         menu->insertAction(firstAction, action_sort_spreadsheet);
  848         menu->insertSeparator(firstAction);
  849     }
  850 
  851     menu->insertAction(firstAction, action_go_to_cell);
  852     menu->insertSeparator(firstAction);
  853     menu->insertAction(firstAction, action_toggle_comments);
  854     menu->insertSeparator(firstAction);
  855     menu->insertAction(firstAction, action_statistics_all_columns);
  856     menu->insertSeparator(firstAction);
  857 }
  858 
  859 /*!
  860  * adds column specific actions in SpreadsheetView to the context menu shown in the project explorer.
  861  */
  862 void SpreadsheetView::createColumnContextMenu(QMenu* menu) {
  863     const Column* column = dynamic_cast<Column*>(QObject::sender());
  864     if (!column)
  865         return; //should never happen, since the sender is always a Column
  866 
  867     QAction* firstAction = menu->actions().at(1);
  868     //TODO: add these menus and synchronize the behavior with the context menu creation
  869     //on the spreadsheet header in eventFilter(),
  870 //      menu->insertMenu(firstAction, m_plotDataMenu);
  871 //      menu->insertMenu(firstAction, m_analyzePlotMenu);
  872 //      menu->insertSeparator(firstAction);
  873 
  874     const bool hasValues = column->hasValues();
  875     const bool numeric = column->isNumeric();
  876     const bool datetime = (column->columnMode() == AbstractColumn::ColumnMode::DateTime);
  877 
  878     if (numeric)
  879         menu->insertMenu(firstAction, m_columnSetAsMenu);
  880 
  881     if (!m_readOnly) {
  882         if (numeric) {
  883             menu->insertSeparator(firstAction);
  884             menu->insertMenu(firstAction, m_columnGenerateDataMenu);
  885             menu->insertSeparator(firstAction);
  886         }
  887         if (numeric || datetime) {
  888             menu->insertMenu(firstAction, m_columnManipulateDataMenu);
  889             menu->insertSeparator(firstAction);
  890         }
  891 
  892         menu->insertMenu(firstAction, m_columnSortMenu);
  893         action_sort_asc_column->setVisible(true);
  894         action_sort_desc_column->setVisible(true);
  895         action_sort_columns->setVisible(false);
  896 
  897         checkColumnMenus(numeric, datetime, hasValues);
  898     }
  899 
  900     menu->insertSeparator(firstAction);
  901     menu->insertAction(firstAction, action_statistics_columns);
  902     action_statistics_columns->setEnabled(numeric && hasValues);
  903 }
  904 
  905 //SLOTS
  906 void SpreadsheetView::handleAspectAdded(const AbstractAspect* aspect) {
  907     const Column* col = dynamic_cast<const Column*>(aspect);
  908     if (!col || col->parentAspect() != m_spreadsheet)
  909         return;
  910 
  911     int index = m_spreadsheet->indexOfChild<Column>(col);
  912     if (col->width() == 0)
  913         m_tableView->resizeColumnToContents(index);
  914     else
  915         m_tableView->setColumnWidth(index, col->width());
  916 
  917     goToCell(0, index);
  918     connect(col, &Column::requestProjectContextMenu, this, &SpreadsheetView::createColumnContextMenu);
  919 }
  920 
  921 void SpreadsheetView::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) {
  922     const Column* col = dynamic_cast<const Column*>(aspect);
  923     if (!col || col->parentAspect() != m_spreadsheet)
  924         return;
  925 
  926     disconnect(col, nullptr, this, nullptr);
  927 }
  928 
  929 void SpreadsheetView::handleHorizontalSectionResized(int logicalIndex, int oldSize, int newSize) {
  930     Q_UNUSED(logicalIndex);
  931     Q_UNUSED(oldSize);
  932 
  933     //save the new size in the column
  934     Column* col = m_spreadsheet->child<Column>(logicalIndex);
  935     col->setWidth(newSize);
  936 }
  937 
  938 void SpreadsheetView::goToCell(int row, int col) {
  939     QModelIndex index = m_model->index(row, col);
  940     m_tableView->scrollTo(index);
  941     m_tableView->setCurrentIndex(index);
  942 }
  943 
  944 void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) {
  945     Q_UNUSED(index);
  946 
  947     static bool inside = false;
  948     if (inside) return;
  949 
  950     Q_ASSERT(index == from);
  951 
  952     inside = true;
  953     m_tableView->horizontalHeader()->moveSection(to, from);
  954     inside = false;
  955     m_spreadsheet->moveColumn(from, to);
  956 }
  957 
  958 //TODO Implement the "change of the column name"-mode upon a double click
  959 void SpreadsheetView::handleHorizontalHeaderDoubleClicked(int index) {
  960     Q_UNUSED(index);
  961 }
  962 
  963 /*!
  964   Returns whether comments are shown currently or not
  965 */
  966 bool SpreadsheetView::areCommentsShown() const {
  967     return m_horizontalHeader->areCommentsShown();
  968 }
  969 
  970 /*!
  971   toggles the column comment in the horizontal header
  972 */
  973 void SpreadsheetView::toggleComments() {
  974     showComments(!areCommentsShown());
  975     //TODO
  976     if (areCommentsShown())
  977         action_toggle_comments->setText(i18n("Hide Comments"));
  978     else
  979         action_toggle_comments->setText(i18n("Show Comments"));
  980 }
  981 
  982 //! Shows (\c on=true) or hides (\c on=false) the column comments in the horizontal header
  983 void SpreadsheetView::showComments(bool on) {
  984     m_horizontalHeader->showComments(on);
  985 }
  986 
  987 void SpreadsheetView::currentColumnChanged(const QModelIndex & current, const QModelIndex & previous) {
  988     Q_UNUSED(previous);
  989     int col = current.column();
  990     if (col < 0 || col >= m_spreadsheet->columnCount())
  991         return;
  992 }
  993 
  994 void SpreadsheetView::handleHeaderDataChanged(Qt::Orientation orientation, int first, int last) {
  995     if (orientation != Qt::Horizontal)
  996         return;
  997 
  998     for (int index = first; index <= last; ++index)
  999         m_tableView->resizeColumnToContents(index);
 1000 }
 1001 
 1002 /*!
 1003   Returns the number of selected columns.
 1004   If \c full is \c true, this function only returns the number of fully selected columns.
 1005 */
 1006 int SpreadsheetView::selectedColumnCount(bool full) const {
 1007     int count = 0;
 1008     const int cols = m_spreadsheet->columnCount();
 1009     for (int i = 0; i < cols; i++)
 1010         if (isColumnSelected(i, full)) count++;
 1011     return count;
 1012 }
 1013 
 1014 /*!
 1015   Returns the number of (at least partly) selected columns with the plot designation \param pd .
 1016  */
 1017 int SpreadsheetView::selectedColumnCount(AbstractColumn::PlotDesignation pd) const{
 1018     int count = 0;
 1019     const int cols = m_spreadsheet->columnCount();
 1020     for (int i = 0; i < cols; i++)
 1021         if ( isColumnSelected(i, false) && (m_spreadsheet->column(i)->plotDesignation() == pd) ) count++;
 1022 
 1023     return count;
 1024 }
 1025 
 1026 /*!
 1027   Returns \c true if column \param col is selected, otherwise returns \c false.
 1028   If \param full is \c true, this function only returns true if the whole column is selected.
 1029 */
 1030 bool SpreadsheetView::isColumnSelected(int col, bool full) const {
 1031     if (full)
 1032         return m_tableView->selectionModel()->isColumnSelected(col, QModelIndex());
 1033     else
 1034         return m_tableView->selectionModel()->columnIntersectsSelection(col, QModelIndex());
 1035 }
 1036 
 1037 /*!
 1038   Returns all selected columns.
 1039   If \param full is true, this function only returns a column if the whole column is selected.
 1040   */
 1041 QVector<Column*> SpreadsheetView::selectedColumns(bool full) const {
 1042     QVector<Column*> columns;
 1043     const int cols = m_spreadsheet->columnCount();
 1044     for (int i = 0; i < cols; i++)
 1045         if (isColumnSelected(i, full)) columns << m_spreadsheet->column(i);
 1046 
 1047     return columns;
 1048 }
 1049 
 1050 /*!
 1051   Returns \c true if row \param row is selected; otherwise returns \c false
 1052   If \param full is \c true, this function only returns \c true if the whole row is selected.
 1053 */
 1054 bool SpreadsheetView::isRowSelected(int row, bool full) const {
 1055     if (full)
 1056         return m_tableView->selectionModel()->isRowSelected(row, QModelIndex());
 1057     else
 1058         return m_tableView->selectionModel()->rowIntersectsSelection(row, QModelIndex());
 1059 }
 1060 
 1061 /*!
 1062   Return the index of the first selected column.
 1063   If \param full is \c true, this function only looks for fully selected columns.
 1064 */
 1065 int SpreadsheetView::firstSelectedColumn(bool full) const {
 1066     const int cols = m_spreadsheet->columnCount();
 1067     for (int i = 0; i < cols; i++) {
 1068         if (isColumnSelected(i, full))
 1069             return i;
 1070     }
 1071     return -1;
 1072 }
 1073 
 1074 /*!
 1075   Return the index of the last selected column.
 1076   If \param full is \c true, this function only looks for fully selected columns.
 1077   */
 1078 int SpreadsheetView::lastSelectedColumn(bool full) const {
 1079     const int cols = m_spreadsheet->columnCount();
 1080     for (int i = cols - 1; i >= 0; i--)
 1081         if (isColumnSelected(i, full)) return i;
 1082 
 1083     return -2;
 1084 }
 1085 
 1086 /*!
 1087   Return the index of the first selected row.
 1088   If \param full is \c true, this function only looks for fully selected rows.
 1089   */
 1090 int SpreadsheetView::firstSelectedRow(bool full) const{
 1091     QModelIndexList indexes;
 1092     if (!full)
 1093         indexes = m_tableView->selectionModel()->selectedIndexes();
 1094     else
 1095         indexes = m_tableView->selectionModel()->selectedRows();
 1096 
 1097     if (!indexes.empty())
 1098         return indexes.first().row();
 1099     else
 1100         return -1;
 1101 }
 1102 
 1103 /*!
 1104   Return the index of the last selected row.
 1105   If \param full is \c true, this function only looks for fully selected rows.
 1106   */
 1107 int SpreadsheetView::lastSelectedRow(bool full) const {
 1108     QModelIndexList indexes;
 1109     if (!full)
 1110         indexes = m_tableView->selectionModel()->selectedIndexes();
 1111     else
 1112         indexes = m_tableView->selectionModel()->selectedRows();
 1113 
 1114     if (!indexes.empty())
 1115         return indexes.last().row();
 1116     else
 1117         return -2;
 1118 }
 1119 
 1120 /*!
 1121   Return whether a cell is selected
 1122  */
 1123 bool SpreadsheetView::isCellSelected(int row, int col) const {
 1124     if (row < 0 || col < 0 || row >= m_spreadsheet->rowCount() || col >= m_spreadsheet->columnCount())
 1125         return false;
 1126 
 1127     return m_tableView->selectionModel()->isSelected(m_model->index(row, col));
 1128 }
 1129 
 1130 /*!
 1131   Get the complete set of selected rows.
 1132  */
 1133 IntervalAttribute<bool> SpreadsheetView::selectedRows(bool full) const {
 1134     IntervalAttribute<bool> result;
 1135     const int rows = m_spreadsheet->rowCount();
 1136     for (int i = 0; i < rows; i++)
 1137         if (isRowSelected(i, full))
 1138             result.setValue(i, true);
 1139     return result;
 1140 }
 1141 
 1142 /*!
 1143   Select/Deselect a cell.
 1144  */
 1145 void SpreadsheetView::setCellSelected(int row, int col, bool select) {
 1146     m_tableView->selectionModel()->select(m_model->index(row, col),
 1147                                           select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
 1148 }
 1149 
 1150 /*!
 1151   Select/Deselect a range of cells.
 1152  */
 1153 void SpreadsheetView::setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select) {
 1154     QModelIndex top_left = m_model->index(first_row, first_col);
 1155     QModelIndex bottom_right = m_model->index(last_row, last_col);
 1156     m_tableView->selectionModel()->select(QItemSelection(top_left, bottom_right),
 1157                                           select ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Deselect);
 1158 }
 1159 
 1160 /*!
 1161   Determine the current cell (-1 if no cell is designated as the current).
 1162  */
 1163 void SpreadsheetView::getCurrentCell(int* row, int* col) const {
 1164     QModelIndex index = m_tableView->selectionModel()->currentIndex();
 1165     if (index.isValid()) {
 1166         *row = index.row();
 1167         *col = index.column();
 1168     } else {
 1169         *row = -1;
 1170         *col = -1;
 1171     }
 1172 }
 1173 
 1174 bool SpreadsheetView::eventFilter(QObject* watched, QEvent* event) {
 1175     if (event->type() == QEvent::ContextMenu) {
 1176         auto* cm_event = static_cast<QContextMenuEvent*>(event);
 1177         const QPoint global_pos = cm_event->globalPos();
 1178         if (watched == m_tableView->verticalHeader()) {
 1179             bool onlyNumeric = true;
 1180             for (int i = 0; i < m_spreadsheet->columnCount(); ++i) {
 1181                 if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::ColumnMode::Numeric) {
 1182                     onlyNumeric = false;
 1183                     break;
 1184                 }
 1185             }
 1186             action_statistics_rows->setVisible(onlyNumeric);
 1187             m_rowMenu->exec(global_pos);
 1188         } else if (watched == m_horizontalHeader) {
 1189             const int col = m_horizontalHeader->logicalIndexAt(cm_event->pos());
 1190             if (!isColumnSelected(col, true)) {
 1191                 QItemSelectionModel* sel_model = m_tableView->selectionModel();
 1192                 sel_model->clearSelection();
 1193                 sel_model->select(QItemSelection(m_model->index(0, col, QModelIndex()),
 1194                                 m_model->index(m_model->rowCount()-1, col, QModelIndex())),
 1195                                 QItemSelectionModel::Select);
 1196             }
 1197 
 1198             if (selectedColumns().size() == 1) {
 1199                 action_sort_columns->setVisible(false);
 1200                 action_sort_asc_column->setVisible(true);
 1201                 action_sort_desc_column->setVisible(true);
 1202             } else {
 1203                 action_sort_columns->setVisible(true);
 1204                 action_sort_asc_column->setVisible(false);
 1205                 action_sort_desc_column->setVisible(false);
 1206             }
 1207 
 1208             //check whether we have non-numeric columns selected and deactivate actions for numeric columns
 1209             bool numeric = true;
 1210             bool plottable = true;
 1211             bool datetime = false;
 1212             bool hasValues = false;
 1213             for (const Column* col : selectedColumns()) {
 1214                 if (!col->isNumeric()) {
 1215                     datetime = (col->columnMode() == AbstractColumn::ColumnMode::DateTime);
 1216                     if (!datetime)
 1217                         plottable = false;
 1218 
 1219                     numeric = false;
 1220                     break;
 1221                 }
 1222             }
 1223 
 1224             for (const Column* col : selectedColumns()) {
 1225                 if (col->hasValues()) {
 1226                     hasValues = true;
 1227                     break;
 1228                 }
 1229             }
 1230 
 1231             m_plotDataMenu->setEnabled(plottable);
 1232             m_analyzePlotMenu->setEnabled(numeric);
 1233             m_columnSetAsMenu->setEnabled(numeric);
 1234             action_statistics_columns->setEnabled(numeric && hasValues);
 1235 
 1236             if (!m_readOnly)
 1237                 checkColumnMenus(numeric, datetime, hasValues);
 1238 
 1239             m_columnMenu->exec(global_pos);
 1240         } else if (watched == this) {
 1241             checkSpreadsheetMenu();
 1242             m_spreadsheetMenu->exec(global_pos);
 1243         }
 1244 
 1245         return true;
 1246     } else if (event->type() == QEvent::KeyPress) {
 1247         auto* key_event = static_cast<QKeyEvent*>(event);
 1248         if (key_event->matches(QKeySequence::Copy))
 1249             copySelection();
 1250         else if (key_event->matches(QKeySequence::Paste))
 1251             pasteIntoSelection();
 1252         else if (key_event->key() == Qt::Key_Backspace || key_event->matches(QKeySequence::Delete))
 1253             clearSelectedCells();
 1254         else if (key_event->key() == Qt::Key_Return || key_event->key() == Qt::Key_Enter)
 1255             advanceCell();
 1256         else if (key_event->key() == Qt::Key_Insert) {
 1257             if (!m_editorEntered) {
 1258                 if (lastSelectedColumn(true) >= 0)
 1259                     insertColumnRight();
 1260                 else
 1261                     insertRowBelow();
 1262             }
 1263         }
 1264     }
 1265 
 1266     return QWidget::eventFilter(watched, event);
 1267 }
 1268 
 1269 /*!
 1270     Advance current cell after [Return] or [Enter] was pressed
 1271 */
 1272 void SpreadsheetView::advanceCell() {
 1273     const QModelIndex& idx = m_tableView->currentIndex();
 1274     const int row = idx.row();
 1275     const int col = idx.column();
 1276     if (row + 1 == m_spreadsheet->rowCount())
 1277         m_spreadsheet->setRowCount(m_spreadsheet->rowCount() + 1);
 1278 
 1279     m_tableView->setCurrentIndex(idx.sibling(row + 1, col));
 1280 }
 1281 
 1282 /*!
 1283  * disables cell data relevant actions in the spreadsheet menu if there're no cells available
 1284  */
 1285 void SpreadsheetView::checkSpreadsheetMenu() {
 1286     const bool cellsAvail = m_spreadsheet->columnCount()>0 && m_spreadsheet->rowCount()>0;
 1287     m_plotDataMenu->setEnabled(cellsAvail);
 1288     m_selectionMenu->setEnabled(cellsAvail);
 1289     action_select_all->setEnabled(cellsAvail);
 1290     action_clear_spreadsheet->setEnabled(cellsAvail);
 1291     action_sort_spreadsheet->setEnabled(cellsAvail);
 1292     action_go_to_cell->setEnabled(cellsAvail);
 1293     action_statistics_all_columns->setEnabled(cellsAvail);
 1294 
 1295     //deactivate the "Clear masks" action for the spreadsheet
 1296     //if there are no masked cells in the spreadsheet
 1297     bool hasMasked = false;
 1298     for (int i = 0; i < m_spreadsheet->columnCount(); ++i) {
 1299         const auto* column = m_spreadsheet->column(i);
 1300         if (column->maskedIntervals().size() > 0) {
 1301             hasMasked = true;
 1302             break;
 1303         }
 1304     }
 1305 
 1306     action_clear_masks->setEnabled(hasMasked);
 1307 
 1308     //deactivate mask/unmask actions for the selection
 1309     //if there are no unmasked/masked cells in the current selection
 1310     QModelIndexList indexes = m_tableView->selectionModel()->selectedIndexes();
 1311     hasMasked = false;
 1312     bool hasUnmasked = false;
 1313     for (auto index : indexes) {
 1314         int row = index.row();
 1315         int col = index.column();
 1316         const auto* column = m_spreadsheet->column(col);
 1317         //TODO: the null pointer check shouldn't be actually required here
 1318         //but when deleting the columns the selection model in the view
 1319         //and the aspect model sometimes get out of sync and we crash...
 1320         if (column && column->isMasked(row)) {
 1321             hasMasked = true;
 1322             break;
 1323         }
 1324     }
 1325 
 1326     for (auto index : indexes) {
 1327         int row = index.row();
 1328         int col = index.column();
 1329         const auto* column = m_spreadsheet->column(col);
 1330         if (column && !column->isMasked(row)) {
 1331             hasUnmasked = true;
 1332             break;
 1333         }
 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     m_spreadsheet->endMacro();
 1699     RESET_CURSOR;
 1700 }
 1701 
 1702 void SpreadsheetView::unmaskSelection() {
 1703     int first = firstSelectedRow();
 1704     if (first < 0) return;
 1705     int last = lastSelectedRow();
 1706 
 1707     WAIT_CURSOR;
 1708     m_spreadsheet->beginMacro(i18n("%1: unmask selected cells", m_spreadsheet->name()));
 1709 
 1710     QVector<CartesianPlot*> plots;
 1711     //determine the dependent plots
 1712     for (auto* column : selectedColumns())
 1713         column->addUsedInPlots(plots);
 1714 
 1715     //suppress retransform in the dependent plots
 1716     for (auto* plot : plots)
 1717         plot->setSuppressDataChangedSignal(true);
 1718 
 1719     //unmask the selected cells
 1720     for (auto* column : selectedColumns()) {
 1721         int col = m_spreadsheet->indexOfChild<Column>(column);
 1722         for (int row = first; row <= last; row++)
 1723             if (isCellSelected(row, col)) column->setMasked(row, false);
 1724     }
 1725 
 1726     //retransform the dependent plots
 1727     for (auto* plot : plots) {
 1728         plot->setSuppressDataChangedSignal(false);
 1729         plot->dataChanged();
 1730     }
 1731 
 1732     m_spreadsheet->endMacro();
 1733     RESET_CURSOR;
 1734 }
 1735 
 1736 void SpreadsheetView::plotData() {
 1737     const QAction* action = dynamic_cast<const QAction*>(QObject::sender());
 1738     PlotDataDialog::PlotType type = PlotDataDialog::PlotType::XYCurve;
 1739     if (action == action_plot_data_xycurve || action == action_plot_data_histogram)
 1740         type = (PlotDataDialog::PlotType)action->data().toInt();
 1741 
 1742     auto* dlg = new PlotDataDialog(m_spreadsheet, type);
 1743 
 1744     if (action != action_plot_data_xycurve && action != action_plot_data_histogram) {
 1745         PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt();
 1746         dlg->setAnalysisAction(type);
 1747     }
 1748 
 1749     dlg->exec();
 1750 }
 1751 
 1752 void SpreadsheetView::fillSelectedCellsWithRowNumbers() {
 1753     if (selectedColumnCount() < 1) return;
 1754     int first = firstSelectedRow();
 1755     if (first < 0) return;
 1756     int last = lastSelectedRow();
 1757 
 1758     WAIT_CURSOR;
 1759     m_spreadsheet->beginMacro(i18n("%1: fill cells with row numbers", m_spreadsheet->name()));
 1760     for (auto* col_ptr : selectedColumns()) {
 1761         int col = m_spreadsheet->indexOfChild<Column>(col_ptr);
 1762         col_ptr->setSuppressDataChangedSignal(true);
 1763         switch (col_ptr->columnMode()) {
 1764         case AbstractColumn::ColumnMode::Numeric: {
 1765             QVector<double> results(last-first+1);
 1766             for (int row = first; row <= last; row++)
 1767                 if (isCellSelected(row, col))
 1768                     results[row-first] = row + 1;
 1769                 else
 1770                     results[row-first] = col_ptr->valueAt(row);
 1771             col_ptr->replaceValues(first, results);
 1772             break;
 1773         }
 1774         case AbstractColumn::ColumnMode::Integer: {
 1775             QVector<int> results(last-first+1);
 1776             for (int row = first; row <= last; row++)
 1777                 if (isCellSelected(row, col))
 1778                     results[row-first] = row + 1;
 1779                 else
 1780                     results[row-first] = col_ptr->integerAt(row);
 1781             col_ptr->replaceInteger(first, results);
 1782             break;
 1783         }
 1784         case AbstractColumn::ColumnMode::BigInt: {
 1785             QVector<qint64> results(last-first+1);
 1786             for (int row = first; row <= last; row++)
 1787                 if (isCellSelected(row, col))
 1788                     results[row-first] = row + 1;
 1789                 else
 1790                     results[row-first] = col_ptr->bigIntAt(row);
 1791             col_ptr->replaceBigInt(first, results);
 1792             break;
 1793         }
 1794         case AbstractColumn::ColumnMode::Text: {
 1795             QVector<QString> results;
 1796             for (int row = first; row <= last; row++)
 1797                 if (isCellSelected(row, col))
 1798                     results << QString::number(row+1);
 1799                 else
 1800                     results << col_ptr->textAt(row);
 1801             col_ptr->replaceTexts(first, results);
 1802             break;
 1803         }
 1804         //TODO: handle other modes
 1805         case AbstractColumn::ColumnMode::DateTime:
 1806         case AbstractColumn::ColumnMode::Month:
 1807         case AbstractColumn::ColumnMode::Day:
 1808             break;
 1809         }
 1810 
 1811         col_ptr->setSuppressDataChangedSignal(false);
 1812         col_ptr->setChanged();
 1813     }
 1814     m_spreadsheet->endMacro();
 1815     RESET_CURSOR;
 1816 }
 1817 
 1818 void SpreadsheetView::fillWithRowNumbers() {
 1819     if (selectedColumnCount() < 1) return;
 1820 
 1821     WAIT_CURSOR;
 1822     m_spreadsheet->beginMacro(i18np("%1: fill column with row numbers",
 1823                                     "%1: fill columns with row numbers",
 1824                                     m_spreadsheet->name(),
 1825                                     selectedColumnCount()));
 1826 
 1827     const int rows = m_spreadsheet->rowCount();
 1828 
 1829     QVector<int> int_data(rows);
 1830     for (int i = 0; i < rows; ++i)
 1831         int_data[i] = i + 1;
 1832 
 1833     for (auto* col : selectedColumns()) {
 1834         switch (col->columnMode()) {
 1835         case AbstractColumn::ColumnMode::Integer:
 1836             col->replaceInteger(0, int_data);
 1837             break;
 1838         case AbstractColumn::ColumnMode::Numeric:
 1839         case AbstractColumn::ColumnMode::BigInt:
 1840             col->setColumnMode(AbstractColumn::ColumnMode::Integer);
 1841             col->replaceInteger(0, int_data);
 1842             break;
 1843         case AbstractColumn::ColumnMode::Text:
 1844         case AbstractColumn::ColumnMode::DateTime:
 1845         case AbstractColumn::ColumnMode::Day:
 1846         case AbstractColumn::ColumnMode::Month:
 1847             break;
 1848         }
 1849     }
 1850 
 1851     m_spreadsheet->endMacro();
 1852     RESET_CURSOR;
 1853 }
 1854 
 1855 //TODO: this function is not used currently.
 1856 void SpreadsheetView::fillSelectedCellsWithRandomNumbers() {
 1857     if (selectedColumnCount() < 1) return;
 1858     int first = firstSelectedRow();
 1859     int last = lastSelectedRow();
 1860     if (first < 0) return;
 1861 
 1862     WAIT_CURSOR;
 1863     m_spreadsheet->beginMacro(i18n("%1: fill cells with random values", m_spreadsheet->name()));
 1864     qsrand(QTime::currentTime().msec());
 1865     for (auto* col_ptr : selectedColumns()) {
 1866         int col = m_spreadsheet->indexOfChild<Column>(col_ptr);
 1867         col_ptr->setSuppressDataChangedSignal(true);
 1868         switch (col_ptr->columnMode()) {
 1869         case AbstractColumn::ColumnMode::Numeric: {
 1870                 QVector<double> results(last-first+1);
 1871                 for (int row = first; row <= last; row++)
 1872                     if (isCellSelected(row, col))
 1873 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
 1874                         results[row-first] = QRandomGenerator::global()->generateDouble();
 1875 #else
 1876                         results[row-first] = double(qrand())/double(RAND_MAX);
 1877 #endif
 1878                     else
 1879                         results[row-first] = col_ptr->valueAt(row);
 1880                 col_ptr->replaceValues(first, results);
 1881                 break;
 1882             }
 1883         case AbstractColumn::ColumnMode::Integer: {
 1884                 QVector<int> results(last-first+1);
 1885                 for (int row = first; row <= last; row++)
 1886                     if (isCellSelected(row, col))
 1887 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
 1888                         results[row-first] = QRandomGenerator::global()->generate();
 1889 #else
 1890                         results[row-first] = qrand();
 1891 #endif
 1892                     else
 1893                         results[row-first] = col_ptr->integerAt(row);
 1894                 col_ptr->replaceInteger(first, results);
 1895                 break;
 1896             }
 1897         case AbstractColumn::ColumnMode::BigInt: {
 1898                 QVector<qint64> results(last-first+1);
 1899                 for (int row = first; row <= last; row++)
 1900                     if (isCellSelected(row, col))
 1901 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
 1902                         results[row-first] = QRandomGenerator::global()->generate64();
 1903 #else
 1904                         results[row-first] = qrand();
 1905 #endif
 1906                     else
 1907                         results[row-first] = col_ptr->bigIntAt(row);
 1908                 col_ptr->replaceBigInt(first, results);
 1909                 break;
 1910             }
 1911         case AbstractColumn::ColumnMode::Text: {
 1912                 QVector<QString> results;
 1913                 for (int row = first; row <= last; row++)
 1914                     if (isCellSelected(row, col))
 1915 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
 1916                         results[row-first] = QString::number(QRandomGenerator::global()->generateDouble());
 1917 #else
 1918                         results << QString::number(double(qrand())/double(RAND_MAX));
 1919 #endif
 1920                     else
 1921                         results << col_ptr->textAt(row);
 1922                 col_ptr->replaceTexts(first, results);
 1923                 break;
 1924             }
 1925         case AbstractColumn::ColumnMode::DateTime:
 1926         case AbstractColumn::ColumnMode::Month:
 1927         case AbstractColumn::ColumnMode::Day: {
 1928                 QVector<QDateTime> results;
 1929                 QDate earliestDate(1, 1, 1);
 1930                 QDate latestDate(2999, 12, 31);
 1931                 QTime midnight(0, 0, 0, 0);
 1932                 for (int row = first; row <= last; row++)
 1933                     if (isCellSelected(row, col))
 1934 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
 1935                         results << QDateTime( earliestDate.addDays((QRandomGenerator::global()->generateDouble())*((double)earliestDate.daysTo(latestDate))), midnight.addMSecs((QRandomGenerator::global()->generateDouble())*1000*60*60*24));
 1936 #else
 1937                         results << QDateTime( earliestDate.addDays(((double)qrand())*((double)earliestDate.daysTo(latestDate))/((double)RAND_MAX)), midnight.addMSecs(((qint64)qrand())*1000*60*60*24/RAND_MAX));
 1938 #endif
 1939                     else
 1940                         results << col_ptr->dateTimeAt(row);
 1941                 col_ptr->replaceDateTimes(first, results);
 1942                 break;
 1943             }
 1944         }
 1945 
 1946         col_ptr->setSuppressDataChangedSignal(false);
 1947         col_ptr->setChanged();
 1948     }
 1949     m_spreadsheet->endMacro();
 1950     RESET_CURSOR;
 1951 }
 1952 
 1953 void SpreadsheetView::fillWithRandomValues() {
 1954     if (selectedColumnCount() < 1) return;
 1955     auto* dlg = new RandomValuesDialog(m_spreadsheet);
 1956     dlg->setColumns(selectedColumns());
 1957     dlg->exec();
 1958 }
 1959 
 1960 void SpreadsheetView::fillWithEquidistantValues() {
 1961     if (selectedColumnCount() < 1) return;
 1962     auto* dlg = new EquidistantValuesDialog(m_spreadsheet);
 1963     dlg->setColumns(selectedColumns());
 1964     dlg->exec();
 1965 }
 1966 
 1967 void SpreadsheetView::fillWithFunctionValues() {
 1968     if (selectedColumnCount() < 1) return;
 1969     auto* dlg = new FunctionValuesDialog(m_spreadsheet);
 1970     dlg->setColumns(selectedColumns());
 1971     dlg->exec();
 1972 }
 1973 
 1974 void SpreadsheetView::fillSelectedCellsWithConstValues() {
 1975     if (selectedColumnCount() < 1) return;
 1976     int first = firstSelectedRow();
 1977     int last = lastSelectedRow();
 1978     if (first < 0)
 1979         return;
 1980 
 1981     bool doubleOk = false;
 1982     bool intOk = false;
 1983     bool bigIntOk = false;
 1984     bool stringOk = false;
 1985     double doubleValue = 0;
 1986     int intValue = 0;
 1987     qint64 bigIntValue = 0;
 1988     QString stringValue;
 1989 
 1990     m_spreadsheet->beginMacro(i18n("%1: fill cells with const values", m_spreadsheet->name()));
 1991     for (auto* col_ptr : selectedColumns()) {
 1992         int col = m_spreadsheet->indexOfChild<Column>(col_ptr);
 1993         col_ptr->setSuppressDataChangedSignal(true);
 1994         switch (col_ptr->columnMode()) {
 1995         case AbstractColumn::ColumnMode::Numeric:
 1996             if (!doubleOk)
 1997                 doubleValue = QInputDialog::getDouble(this, i18n("Fill the selection with constant value"),
 1998                     i18n("Value"), 0, -std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), 6, &doubleOk);
 1999             if (doubleOk) {
 2000                 WAIT_CURSOR;
 2001                 QVector<double> results(last-first+1);
 2002                 for (int row = first; row <= last; row++) {
 2003                     if (isCellSelected(row, col))
 2004                         results[row-first] = doubleValue;
 2005                     else
 2006                         results[row-first] = col_ptr->valueAt(row);
 2007                 }
 2008                 col_ptr->replaceValues(first, results);
 2009                 RESET_CURSOR;
 2010             }
 2011             break;
 2012         case AbstractColumn::ColumnMode::Integer:
 2013             if (!intOk)
 2014                 intValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"),
 2015                     i18n("Value"), 0, -2147483647, 2147483647, 1, &intOk);
 2016             if (intOk) {
 2017                 WAIT_CURSOR;
 2018                 QVector<int> results(last-first+1);
 2019                 for (int row = first; row <= last; row++) {
 2020                     if (isCellSelected(row, col))
 2021                         results[row-first] = intValue;
 2022                     else
 2023                         results[row-first] = col_ptr->integerAt(row);
 2024                 }
 2025                 col_ptr->replaceInteger(first, results);
 2026                 RESET_CURSOR;
 2027             }
 2028             break;
 2029         case AbstractColumn::ColumnMode::BigInt:
 2030             //TODO: getBigInt()
 2031             if (!bigIntOk)
 2032                 bigIntValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"),
 2033                     i18n("Value"), 0, -2147483647, 2147483647, 1, &bigIntOk);
 2034             if (bigIntOk) {
 2035                 WAIT_CURSOR;
 2036                 QVector<qint64> results(last-first+1);
 2037                 for (int row = first; row <= last; row++) {
 2038                     if (isCellSelected(row, col))
 2039                         results[row-first] = bigIntValue;
 2040                     else
 2041                         results[row-first] = col_ptr->bigIntAt(row);
 2042                 }
 2043                 col_ptr->replaceBigInt(first, results);
 2044                 RESET_CURSOR;
 2045             }
 2046             break;
 2047         case AbstractColumn::ColumnMode::Text:
 2048             if (!stringOk)
 2049                 stringValue = QInputDialog::getText(this, i18n("Fill the selection with constant value"),
 2050                     i18n("Value"), QLineEdit::Normal, nullptr, &stringOk);
 2051             if (stringOk && !stringValue.isEmpty()) {
 2052                 WAIT_CURSOR;
 2053                 QVector<QString> results;
 2054                 for (int row = first; row <= last; row++) {
 2055                     if (isCellSelected(row, col))
 2056                         results << stringValue;
 2057                     else
 2058                         results << col_ptr->textAt(row);
 2059                 }
 2060                 col_ptr->replaceTexts(first, results);
 2061                 RESET_CURSOR;
 2062             }
 2063             break;
 2064         //TODO: handle other modes
 2065         case AbstractColumn::ColumnMode::DateTime:
 2066         case AbstractColumn::ColumnMode::Month:
 2067         case AbstractColumn::ColumnMode::Day:
 2068             break;
 2069         }
 2070 
 2071         col_ptr->setSuppressDataChangedSignal(false);
 2072         col_ptr->setChanged();
 2073     }
 2074     m_spreadsheet->endMacro();
 2075 }
 2076 
 2077 /*!
 2078     Open the sort dialog for all columns.
 2079 */
 2080 void SpreadsheetView::sortSpreadsheet() {
 2081     sortDialog(m_spreadsheet->children<Column>());
 2082 }
 2083 
 2084 /*!
 2085   Insert an empty column left to the first selected column
 2086 */
 2087 void SpreadsheetView::insertColumnLeft() {
 2088     insertColumnsLeft(1);
 2089 }
 2090 
 2091 /*!
 2092   Insert multiple empty columns left to the firt selected column
 2093 */
 2094 void SpreadsheetView::insertColumnsLeft() {
 2095     bool ok = false;
 2096     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);
 2097     if (!ok)
 2098         return;
 2099 
 2100     insertColumnsLeft(count);
 2101 }
 2102 
 2103 /*!
 2104  * private helper function doing the actual insertion of columns to the left
 2105  */
 2106 void SpreadsheetView::insertColumnsLeft(int count) {
 2107     WAIT_CURSOR;
 2108     m_spreadsheet->beginMacro(i18np("%1: insert empty column",
 2109                                     "%1: insert empty columns",
 2110                                  m_spreadsheet->name(),
 2111                                  count
 2112                             ));
 2113 
 2114     const int first = firstSelectedColumn();
 2115 
 2116     if (first >= 0) {
 2117         //determine the first selected column
 2118         Column* firstCol = m_spreadsheet->child<Column>(first);
 2119 
 2120         for (int i = 0; i < count; ++i) {
 2121             Column* newCol = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Numeric);
 2122             newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2123 
 2124             //resize the new column and insert it before the first selected column
 2125             newCol->insertRows(0, m_spreadsheet->rowCount());
 2126             m_spreadsheet->insertChildBefore(newCol, firstCol);
 2127         }
 2128     } else {
 2129         if (m_spreadsheet->columnCount()>0) {
 2130             //columns available but no columns selected -> prepend the new column at the very beginning
 2131             Column* firstCol = m_spreadsheet->child<Column>(0);
 2132 
 2133             for (int i = 0; i < count; ++i) {
 2134                 Column* newCol = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Numeric);
 2135                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2136                 newCol->insertRows(0, m_spreadsheet->rowCount());
 2137                 m_spreadsheet->insertChildBefore(newCol, firstCol);
 2138             }
 2139         } else {
 2140             //no columns available anymore -> resize the spreadsheet and the new column to the default size
 2141             KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet"));
 2142             const int rows = group.readEntry(QLatin1String("RowCount"), 100);
 2143             m_spreadsheet->setRowCount(rows);
 2144 
 2145             for (int i = 0; i < count; ++i) {
 2146                 Column* newCol = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Numeric);
 2147                 (i == 0) ? newCol->setPlotDesignation(AbstractColumn::PlotDesignation::X) : newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2148                 newCol->insertRows(0, rows);
 2149 
 2150                 //add/append a new column
 2151                 m_spreadsheet->addChild(newCol);
 2152             }
 2153         }
 2154     }
 2155 
 2156     m_spreadsheet->endMacro();
 2157     RESET_CURSOR;
 2158 }
 2159 
 2160 /*!
 2161   Insert an empty column right to the last selected column
 2162 */
 2163 void SpreadsheetView::insertColumnRight() {
 2164     insertColumnsRight(1);
 2165 }
 2166 
 2167 /*!
 2168   Insert multiple empty columns right to the last selected column
 2169 */
 2170 void SpreadsheetView::insertColumnsRight() {
 2171     bool ok = false;
 2172     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);
 2173     if (!ok)
 2174         return;
 2175 
 2176     insertColumnsRight(count);
 2177 }
 2178 
 2179 /*!
 2180  * private helper function doing the actual insertion of columns to the right
 2181  */
 2182 void SpreadsheetView::insertColumnsRight(int count) {
 2183     WAIT_CURSOR;
 2184     m_spreadsheet->beginMacro(i18np("%1: insert empty column",
 2185                                     "%1: insert empty columns",
 2186                                     m_spreadsheet->name(),
 2187                                     count
 2188                             ));
 2189 
 2190     const int last = lastSelectedColumn();
 2191 
 2192     if (last >= 0) {
 2193         if (last < m_spreadsheet->columnCount() - 1) {
 2194             //determine the column next to the last selected column
 2195             Column* nextCol = m_spreadsheet->child<Column>(last + 1);
 2196 
 2197             for (int i = 0; i < count; ++i) {
 2198                 Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric);
 2199                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2200                 newCol->insertRows(0, m_spreadsheet->rowCount());
 2201 
 2202                 //insert the new column before the column next to the last selected column
 2203                 m_spreadsheet->insertChildBefore(newCol, nextCol);
 2204             }
 2205         } else {
 2206             for (int i = 0; i < count; ++i) {
 2207                 Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric);
 2208                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2209                 newCol->insertRows(0, m_spreadsheet->rowCount());
 2210 
 2211                 //last column selected, no next column available -> add/append a new column
 2212                 m_spreadsheet->addChild(newCol);
 2213             }
 2214         }
 2215     } else {
 2216         if (m_spreadsheet->columnCount()>0) {
 2217             for (int i = 0; i < count; ++i) {
 2218                 Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric);
 2219                 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2220                 newCol->insertRows(0, m_spreadsheet->rowCount());
 2221 
 2222                 //columns available but no columns selected -> append the new column at the very end
 2223                 m_spreadsheet->addChild(newCol);
 2224             }
 2225         } else {
 2226             //no columns available anymore -> resize the spreadsheet and the new column to the default size
 2227             KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet"));
 2228             const int rows = group.readEntry(QLatin1String("RowCount"), 100);
 2229             m_spreadsheet->setRowCount(rows);
 2230 
 2231             for (int i = 0; i < count; ++i) {
 2232                 Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric);
 2233                 (i == 0) ? newCol->setPlotDesignation(AbstractColumn::PlotDesignation::X) : newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
 2234                 newCol->insertRows(0, rows);
 2235 
 2236                 //add/append a new column
 2237                 m_spreadsheet->addChild(newCol);
 2238             }
 2239         }
 2240     }
 2241 
 2242     m_spreadsheet->endMacro();
 2243     RESET_CURSOR;
 2244 }
 2245 
 2246 void SpreadsheetView::removeSelectedColumns() {
 2247     WAIT_CURSOR;
 2248     m_spreadsheet->beginMacro(i18n("%1: remove selected columns", m_spreadsheet->name()));
 2249 
 2250     for (auto* column : selectedColumns())
 2251         m_spreadsheet->removeChild(column);
 2252 
 2253     m_spreadsheet->endMacro();
 2254     RESET_CURSOR;
 2255 }
 2256 
 2257 void SpreadsheetView::clearSelectedColumns() {
 2258     WAIT_CURSOR;
 2259     m_spreadsheet->beginMacro(i18n("%1: clear selected columns", m_spreadsheet->name()));
 2260 
 2261 //  if (formulaModeActive()) {
 2262 //      for (auto* col : selectedColumns()) {
 2263 //          col->setSuppressDataChangedSignal(true);
 2264 //          col->clearFormulas();
 2265 //          col->setSuppressDataChangedSignal(false);
 2266 //          col->setChanged();
 2267 //      }
 2268 //  } else {
 2269     for (auto* col : selectedColumns()) {
 2270         col->setSuppressDataChangedSignal(true);
 2271         col->clear();
 2272         col->setSuppressDataChangedSignal(false);
 2273         col->setChanged();
 2274     }
 2275 
 2276     m_spreadsheet->endMacro();
 2277     RESET_CURSOR;
 2278 }
 2279 
 2280 void SpreadsheetView::setSelectionAs() {
 2281     QVector<Column*> columns = selectedColumns();
 2282     if (!columns.size())
 2283         return;
 2284 
 2285     m_spreadsheet->beginMacro(i18n("%1: set plot designation", m_spreadsheet->name()));
 2286 
 2287     QAction* action = dynamic_cast<QAction*>(QObject::sender());
 2288     if (!action)
 2289         return;
 2290 
 2291     auto pd = (AbstractColumn::PlotDesignation)action->data().toInt();
 2292     for (auto* col : columns)
 2293         col->setPlotDesignation(pd);
 2294 
 2295     m_spreadsheet->endMacro();
 2296 }
 2297 
 2298 /*!
 2299  * add, subtract, multiply, divide
 2300  */
 2301 void SpreadsheetView::modifyValues() {
 2302     if (selectedColumnCount() < 1)
 2303         return;
 2304 
 2305     const QAction* action = dynamic_cast<const QAction*>(QObject::sender());
 2306     auto op = (AddSubtractValueDialog::Operation)action->data().toInt();
 2307     auto* dlg = new AddSubtractValueDialog(m_spreadsheet, op);
 2308     dlg->setColumns(selectedColumns());
 2309     dlg->exec();
 2310 }
 2311 
 2312 void SpreadsheetView::reverseColumns() {
 2313     WAIT_CURSOR;
 2314     QVector<Column*> cols = selectedColumns();
 2315     m_spreadsheet->beginMacro(i18np("%1: reverse column", "%1: reverse columns",
 2316         m_spreadsheet->name(), cols.size()));
 2317     for (auto* col : cols) {
 2318         if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) {
 2319             //determine the last row containing a valid value,
 2320             //ignore all following empty rows when doing the reverse
 2321             auto* data = static_cast<QVector<double>* >(col->data());
 2322             QVector<double> new_data(*data);
 2323             auto itEnd = new_data.begin();
 2324             auto it = new_data.begin();
 2325             while (it != new_data.end()) {
 2326                 if (!std::isnan(*it))
 2327                     itEnd = it;
 2328                 ++it;
 2329             }
 2330             ++itEnd;
 2331 
 2332             std::reverse(new_data.begin(), itEnd);
 2333             col->replaceValues(0, new_data);
 2334         } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) {
 2335             auto* data = static_cast<QVector<int>* >(col->data());
 2336             QVector<int> new_data(*data);
 2337             std::reverse(new_data.begin(), new_data.end());
 2338             col->replaceInteger(0, new_data);
 2339         } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) {
 2340             auto* data = static_cast<QVector<qint64>* >(col->data());
 2341             QVector<qint64> new_data(*data);
 2342             std::reverse(new_data.begin(), new_data.end());
 2343             col->replaceBigInt(0, new_data);
 2344         }
 2345     }
 2346     m_spreadsheet->endMacro();
 2347     RESET_CURSOR;
 2348 }
 2349 
 2350 void SpreadsheetView::dropColumnValues() {
 2351     if (selectedColumnCount() < 1) return;
 2352     auto* dlg = new DropValuesDialog(m_spreadsheet);
 2353     dlg->setColumns(selectedColumns());
 2354     dlg->exec();
 2355 }
 2356 
 2357 void SpreadsheetView::maskColumnValues() {
 2358     if (selectedColumnCount() < 1) return;
 2359     auto* dlg = new DropValuesDialog(m_spreadsheet, true);
 2360     dlg->setColumns(selectedColumns());
 2361     dlg->exec();
 2362 }
 2363 
 2364 // void SpreadsheetView::joinColumns() {
 2365 //  //TODO
 2366 // }
 2367 
 2368 void SpreadsheetView::normalizeSelectedColumns(QAction* action) {
 2369     auto columns = selectedColumns();
 2370     if (columns.isEmpty())
 2371         return;
 2372 
 2373     auto method = static_cast<NormalizationMethod>(action->data().toInt());
 2374 
 2375     double rescaleIntervalMin = 0.0;
 2376     double rescaleIntervalMax = 0.0;
 2377     if (method == Rescale) {
 2378         auto* dlg = new RescaleDialog(this);
 2379         dlg->setColumns(columns);
 2380         int rc = dlg->exec();
 2381         if (rc != QDialog::Accepted)
 2382             return;
 2383 
 2384         rescaleIntervalMin = dlg->min();
 2385         rescaleIntervalMax = dlg->max();
 2386         delete dlg;
 2387     }
 2388 
 2389     WAIT_CURSOR;
 2390     QStringList messages;
 2391     QString message = i18n("Normalization of the column <i>%1</i> was not possible because of %2.");
 2392     m_spreadsheet->beginMacro(i18n("%1: normalize columns", m_spreadsheet->name()));
 2393 
 2394     for (auto* col : columns) {
 2395         if (col->columnMode() != AbstractColumn::ColumnMode::Numeric
 2396             && col->columnMode() != AbstractColumn::ColumnMode::Integer
 2397             && col->columnMode() != AbstractColumn::ColumnMode::BigInt)
 2398             continue;
 2399 
 2400         if (col->columnMode() == AbstractColumn::ColumnMode::Integer
 2401             || col->columnMode() == AbstractColumn::ColumnMode::BigInt)
 2402             col->setColumnMode(AbstractColumn::ColumnMode::Numeric);
 2403 
 2404         auto* data = static_cast<QVector<double>* >(col->data());
 2405         QVector<double> new_data(col->rowCount());
 2406 
 2407         switch (method) {
 2408         case DivideBySum: {
 2409             double sum = std::accumulate(data->begin(), data->end(), 0);
 2410             if (sum != 0.0) {
 2411                 for (int i = 0; i < col->rowCount(); ++i)
 2412                     new_data[i] = data->operator[](i) / sum;
 2413             } else {
 2414                 messages << message.arg(col->name()).arg(QLatin1String("Sum = 0"));
 2415                 continue;
 2416             }
 2417             break;
 2418         }
 2419         case DivideByMin: {
 2420             double min = col->minimum();
 2421             if (min != 0.0) {
 2422                 for (int i = 0; i < col->rowCount(); ++i)
 2423                     new_data[i] = data->operator[](i) / min;
 2424             } else {
 2425                 messages << message.arg(col->name()).arg(QLatin1String("Min = 0"));
 2426                 continue;
 2427             }
 2428             break;
 2429         }
 2430         case DivideByMax: {
 2431             double max = col->maximum();
 2432             if (max != 0.0) {
 2433                 for (int i = 0; i < col->rowCount(); ++i)
 2434                     new_data[i] = data->operator[](i) / max;
 2435             } else {
 2436                 messages << message.arg(col->name()).arg(QLatin1String("Max = 0"));
 2437                 continue;
 2438             }
 2439             break;
 2440         }
 2441         case DivideByCount: {
 2442             int count = data->size();
 2443             if (count != 0.0) {
 2444                 for (int i = 0; i < col->rowCount(); ++i)
 2445                     new_data[i] = data->operator[](i) / count;
 2446             } else {
 2447                 messages << message.arg(col->name()).arg(QLatin1String("Count = 0"));
 2448                 continue;
 2449             }
 2450             break;
 2451         }
 2452         case DivideByMean: {
 2453             double mean = col->statistics().arithmeticMean;
 2454             if (mean != 0.0) {
 2455                 for (int i = 0; i < col->rowCount(); ++i)
 2456                     new_data[i] = data->operator[](i) / mean;
 2457             } else {
 2458                 messages << message.arg(col->name()).arg(QLatin1String("Mean = 0"));
 2459                 continue;
 2460             }
 2461             break;
 2462         }
 2463         case DivideByMedian: {
 2464             double median = col->statistics().median;
 2465             if (median != 0.0) {
 2466                 for (int i = 0; i < col->rowCount(); ++i)
 2467                     new_data[i] = data->operator[](i) / median;
 2468             } else {
 2469                 messages << message.arg(col->name()).arg(QLatin1String("Median = 0"));
 2470                 continue;
 2471             }
 2472             break;
 2473         }
 2474         case DivideByMode: {
 2475             double mode = col->statistics().mode;
 2476             if (mode != 0.0 && !std::isnan(mode)) {
 2477                 for (int i = 0; i < col->rowCount(); ++i)
 2478                     new_data[i] = data->operator[](i) / mode;
 2479             } else {
 2480                 if (mode == 0.0)
 2481                     messages << message.arg(col->name()).arg(QLatin1String("Mode = 0"));
 2482                 else
 2483                     messages << message.arg(col->name()).arg(i18n("'Mode not defined'"));
 2484                 continue;
 2485             }
 2486             break;
 2487         }
 2488         case DivideByRange: {
 2489             double range = col->statistics().maximum - col->statistics().minimum;
 2490             if (range != 0.0) {
 2491                 for (int i = 0; i < col->rowCount(); ++i)
 2492                     new_data[i] = data->operator[](i) / range;
 2493             } else {
 2494                 messages << message.arg(col->name()).arg(QLatin1String("Range = 0"));
 2495                 continue;
 2496             }
 2497             break;
 2498         }
 2499         case DivideBySD: {
 2500             double std = col->statistics().standardDeviation;
 2501             if (std != 0.0) {
 2502                 for (int i = 0; i < col->rowCount(); ++i)
 2503                     new_data[i] = data->operator[](i) / std;
 2504             } else {
 2505                 messages << message.arg(col->name()).arg(QLatin1String("SD = 0"));
 2506                 continue;
 2507             }
 2508             break;
 2509         }
 2510         case DivideByMAD: {
 2511             double mad = col->statistics().medianDeviation;
 2512             if (mad != 0.0) {
 2513                 for (int i = 0; i < col->rowCount(); ++i)
 2514                     new_data[i] = data->operator[](i) / mad;
 2515             } else {
 2516                 messages << message.arg(col->name()).arg(QLatin1String("MAD = 0"));
 2517                 continue;
 2518             }
 2519             break;
 2520         }
 2521         case DivideByIQR: {
 2522             double iqr = col->statistics().iqr;
 2523             if (iqr != 0.0) {
 2524                 for (int i = 0; i < col->rowCount(); ++i)
 2525                     new_data[i] = data->operator[](i) / iqr;
 2526             } else {
 2527                 messages << message.arg(col->name()).arg(QLatin1String("IQR = 0"));
 2528                 continue;
 2529             }
 2530             break;
 2531         }
 2532         case ZScoreSD: {
 2533             double mean = col->statistics().arithmeticMean;
 2534             double std = col->statistics().standardDeviation;
 2535             if (std != 0.0) {
 2536                 for (int i = 0; i < col->rowCount(); ++i)
 2537                     new_data[i] = (data->operator[](i) - mean) / std;
 2538             } else {
 2539                 messages << message.arg(col->name()).arg(QLatin1String("SD = 0"));
 2540                 continue;
 2541             }
 2542             break;
 2543         }
 2544         case ZScoreMAD: {
 2545             double median = col->statistics().median;
 2546             double mad = col->statistics().medianDeviation;
 2547             if (mad != 0.0) {
 2548                 for (int i = 0; i < col->rowCount(); ++i)
 2549                     new_data[i] = (data->operator[](i) - median) / mad;
 2550             } else {
 2551                 messages << message.arg(col->name()).arg(QLatin1String("MAD = 0"));
 2552                 continue;
 2553             }
 2554             break;
 2555         }
 2556         case ZScoreIQR: {
 2557             double median = col->statistics().median;
 2558             double iqr = col->statistics().thirdQuartile - col->statistics().firstQuartile;
 2559             if (iqr != 0.0) {
 2560                 for (int i = 0; i < col->rowCount(); ++i)
 2561                     new_data[i] = (data->operator[](i) - median) / iqr;
 2562             } else {
 2563                 messages << message.arg(col->name()).arg(QLatin1String("IQR = 0"));
 2564                 continue;
 2565             }
 2566             break;
 2567         }
 2568         case Rescale: {
 2569             double min = col->statistics().minimum;
 2570             double max = col->statistics().maximum;
 2571             if (max - min != 0.0) {
 2572                 for (int i = 0; i < col->rowCount(); ++i)
 2573                     new_data[i] = rescaleIntervalMin + (data->operator[](i) - min)/(max - min)*(rescaleIntervalMax - rescaleIntervalMin);
 2574             } else {
 2575                 messages << message.arg(col->name()).arg(QLatin1String("Max - Min = 0"));
 2576                 continue;
 2577             }
 2578             break;
 2579         }
 2580         }
 2581 
 2582         col->replaceValues(0, new_data);
 2583     }
 2584     m_spreadsheet->endMacro();
 2585     RESET_CURSOR;
 2586 
 2587     if (!messages.isEmpty()) {
 2588         QString info;
 2589         for (const QString& message : messages) {
 2590             if (!info.isEmpty())
 2591                 info += QLatin1String("<br><br>");
 2592             info += message;
 2593         }
 2594         QMessageBox::warning(this, i18n("Normalization not possible"), info);
 2595     }
 2596 }
 2597 
 2598 void SpreadsheetView::powerTransformSelectedColumns(QAction* action) {
 2599     auto columns = selectedColumns();
 2600     if (columns.isEmpty())
 2601         return;
 2602 
 2603     auto power = static_cast<TukeyLadderPower>(action->data().toInt());
 2604 
 2605     WAIT_CURSOR;
 2606     m_spreadsheet->beginMacro(i18n("%1: power transform columns", m_spreadsheet->name()));
 2607 
 2608     for (auto* col : columns) {
 2609         if (col->columnMode() != AbstractColumn::ColumnMode::Numeric
 2610             && col->columnMode() != AbstractColumn::ColumnMode::Integer
 2611             && col->columnMode() != AbstractColumn::ColumnMode::BigInt)
 2612             continue;
 2613 
 2614         if (col->columnMode() == AbstractColumn::ColumnMode::Integer
 2615             || col->columnMode() == AbstractColumn::ColumnMode::BigInt)
 2616             col->setColumnMode(AbstractColumn::ColumnMode::Numeric);
 2617 
 2618         auto* data = static_cast<QVector<double>* >(col->data());
 2619         QVector<double> new_data(col->rowCount());
 2620 
 2621         switch (power) {
 2622         case InverseSquared: {
 2623             for (int i = 0; i < col->rowCount(); ++i) {
 2624                 double x = data->operator[](i);
 2625                 if (x != 0.0)
 2626                     new_data[i] = 1 / gsl_pow_2(x);
 2627                 else
 2628                     new_data[i] = NAN;
 2629             }
 2630             break;
 2631         }
 2632         case Inverse: {
 2633             for (int i = 0; i < col->rowCount(); ++i) {
 2634                 double x = data->operator[](i);
 2635                 if (x != 0.0)
 2636                     new_data[i] = 1 / x;
 2637                 else
 2638                     new_data[i] = NAN;
 2639             }
 2640             break;
 2641         }
 2642         case InverseSquareRoot: {
 2643             for (int i = 0; i < col->rowCount(); ++i) {
 2644                 double x = data->operator[](i);
 2645                 if (x >= 0.0)
 2646                     new_data[i] = 1 / std::sqrt(x);
 2647                 else
 2648                     new_data[i] = NAN;
 2649             }
 2650             break;
 2651         }
 2652         case Log: {
 2653             for (int i = 0; i < col->rowCount(); ++i) {
 2654                 double x = data->operator[](i);
 2655                 if (x >= 0.0)
 2656                     new_data[i] = log10(x);
 2657                 else
 2658                     new_data[i] = NAN;
 2659             }
 2660             break;
 2661         }
 2662         case SquareRoot: {
 2663             for (int i = 0; i < col->rowCount(); ++i) {
 2664                 double x = data->operator[](i);
 2665                 if (x >= 0.0)
 2666                     new_data[i] = std::sqrt(x);
 2667                 else
 2668                     new_data[i] = NAN;
 2669             }
 2670             break;
 2671         }
 2672         case Squared: {
 2673             for (int i = 0; i < col->rowCount(); ++i) {
 2674                 double x = data->operator[](i);
 2675                 new_data[i] = gsl_pow_2(x);
 2676             }
 2677             break;
 2678         }
 2679         case Cube: {
 2680             for (int i = 0; i < col->rowCount(); ++i) {
 2681                 double x = data->operator[](i);
 2682                 new_data[i] = gsl_pow_3(x);
 2683             }
 2684             break;
 2685         }
 2686         }
 2687 
 2688         col->replaceValues(0, new_data);
 2689     }
 2690 
 2691     m_spreadsheet->endMacro();
 2692     RESET_CURSOR;
 2693 }
 2694 
 2695 //TODO: either make this complete (support all column modes and normalization methods) or remove this code completely.
 2696 /*
 2697 void SpreadsheetView::normalizeSelection() {
 2698     WAIT_CURSOR;
 2699     m_spreadsheet->beginMacro(i18n("%1: normalize selection", m_spreadsheet->name()));
 2700     double max = 0.0;
 2701     for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++)
 2702         if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Numeric)
 2703             for (int row = 0; row < m_spreadsheet->rowCount(); row++) {
 2704                 if (isCellSelected(row, col) && m_spreadsheet->column(col)->valueAt(row) > max)
 2705                     max = m_spreadsheet->column(col)->valueAt(row);
 2706             }
 2707 
 2708     if (max != 0.0) { // avoid division by zero
 2709         //TODO setSuppressDataChangedSignal
 2710         for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++)
 2711             if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Numeric)
 2712                 for (int row = 0; row < m_spreadsheet->rowCount(); row++) {
 2713                     if (isCellSelected(row, col))
 2714                         m_spreadsheet->column(col)->setValueAt(row, m_spreadsheet->column(col)->valueAt(row) / max);
 2715                 }
 2716     }
 2717     m_spreadsheet->endMacro();
 2718     RESET_CURSOR;
 2719 }
 2720 */
 2721 void SpreadsheetView::sortSelectedColumns() {
 2722     sortDialog(selectedColumns());
 2723 }
 2724 
 2725 void SpreadsheetView::showAllColumnsStatistics() {
 2726     showColumnStatistics(true);
 2727 }
 2728 
 2729 void SpreadsheetView::showColumnStatistics(bool forAll) {
 2730     QString dlgTitle(m_spreadsheet->name() + " column statistics");
 2731     QVector<Column*> columns;
 2732 
 2733     if (!forAll)
 2734         columns = selectedColumns();
 2735     else if (forAll) {
 2736         for (int col = 0; col < m_spreadsheet->columnCount(); ++col) {
 2737             if (m_spreadsheet->column(col)->isNumeric())
 2738                 columns << m_spreadsheet->column(col);
 2739         }
 2740     }
 2741 
 2742     auto* dlg = new StatisticsDialog(dlgTitle, columns);
 2743     dlg->setModal(true);
 2744     dlg->show();
 2745     QApplication::processEvents(QEventLoop::AllEvents, 0);
 2746     QTimer::singleShot(0, this, [=] () {dlg->showStatistics();});
 2747 }
 2748 
 2749 void SpreadsheetView::showRowStatistics() {
 2750     QString dlgTitle(m_spreadsheet->name() + " row statistics");
 2751 
 2752     QVector<Column*> columns;
 2753     for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
 2754         if (isRowSelected(i)) {
 2755             QVector<double> rowValues;
 2756             for (int j = 0; j < m_spreadsheet->columnCount(); ++j)
 2757                 rowValues << m_spreadsheet->column(j)->valueAt(i);
 2758             columns << new Column(i18n("Row %1").arg(i+1), rowValues);
 2759         }
 2760     }
 2761     auto* dlg = new StatisticsDialog(dlgTitle, columns);
 2762     dlg->showStatistics();
 2763 
 2764     if (dlg->exec() == QDialog::Accepted) {
 2765         qDeleteAll(columns);
 2766         columns.clear();
 2767     }
 2768 }
 2769 
 2770 /*!
 2771   Insert an empty row above(=before) the first selected row
 2772 */
 2773 void SpreadsheetView::insertRowAbove() {
 2774     insertRowsAbove(1);
 2775 }
 2776 
 2777 /*!
 2778   Insert multiple empty rows above(=before) the first selected row
 2779 */
 2780 void SpreadsheetView::insertRowsAbove() {
 2781     bool ok = false;
 2782     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);
 2783     if (ok)
 2784         insertRowsAbove(count);
 2785 }
 2786 
 2787 /*!
 2788  * private helper function doing the actual insertion of rows above
 2789  */
 2790 void SpreadsheetView::insertRowsAbove(int count) {
 2791     int first = firstSelectedRow();
 2792     if (first < 0)
 2793         return;
 2794 
 2795     WAIT_CURSOR;
 2796     m_spreadsheet->beginMacro(i18np("%1: insert empty row",
 2797                                     "%1: insert empty rows",
 2798                                     m_spreadsheet->name(),
 2799                                     count
 2800                             ));
 2801     m_spreadsheet->insertRows(first, count);
 2802     m_spreadsheet->endMacro();
 2803     RESET_CURSOR;
 2804 }
 2805 
 2806 /*!
 2807   Insert an empty row below the last selected row
 2808 */
 2809 void SpreadsheetView::insertRowBelow() {
 2810     insertRowsBelow(1);
 2811 }
 2812 
 2813 /*!
 2814   Insert an empty row below the last selected row
 2815 */
 2816 void SpreadsheetView::insertRowsBelow() {
 2817     bool ok = false;
 2818     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);
 2819     if (ok)
 2820         insertRowsBelow(count);
 2821 }
 2822 
 2823 /*!
 2824  * private helper function doing the actual insertion of rows below
 2825  */
 2826 void SpreadsheetView::insertRowsBelow(int count) {
 2827     int last = lastSelectedRow();
 2828     if (last < 0)
 2829         return;
 2830 
 2831     WAIT_CURSOR;
 2832     m_spreadsheet->beginMacro(i18np("%1: insert empty row",
 2833                                    "%1: insert empty rows",
 2834                                     m_spreadsheet->name(),
 2835                                     count
 2836                             ));
 2837 
 2838     if (last < m_spreadsheet->rowCount() - 1)
 2839         m_spreadsheet->insertRows(last + 1, count); //insert before the next to the last selected row
 2840     else
 2841         m_spreadsheet->appendRows(count); //append new rows at the end
 2842 
 2843     m_spreadsheet->endMacro();
 2844     RESET_CURSOR;
 2845 }
 2846 
 2847 void SpreadsheetView::removeSelectedRows() {
 2848     if (firstSelectedRow() < 0) return;
 2849 
 2850     WAIT_CURSOR;
 2851     m_spreadsheet->beginMacro(i18n("%1: remove selected rows", m_spreadsheet->name()));
 2852     //TODO setSuppressDataChangedSignal
 2853     for (const auto& i : selectedRows().intervals())
 2854         m_spreadsheet->removeRows(i.start(), i.size());
 2855     m_spreadsheet->endMacro();
 2856     RESET_CURSOR;
 2857 }
 2858 
 2859 void SpreadsheetView::clearSelectedRows() {
 2860     if (firstSelectedRow() < 0) return;
 2861 
 2862     WAIT_CURSOR;
 2863     m_spreadsheet->beginMacro(i18n("%1: clear selected rows", m_spreadsheet->name()));
 2864     for (auto* col : selectedColumns()) {
 2865         col->setSuppressDataChangedSignal(true);
 2866 //      if (formulaModeActive()) {
 2867 //          for (const auto& i : selectedRows().intervals())
 2868 //              col->setFormula(i, QString());
 2869 //      } else {
 2870         for (const auto& i : selectedRows().intervals()) {
 2871             if (i.end() == col->rowCount()-1)
 2872                 col->removeRows(i.start(), i.size());
 2873             else {
 2874                 QVector<QString> empties;
 2875                 for (int j = 0; j < i.size(); j++)
 2876                     empties << QString();
 2877                 col->asStringColumn()->replaceTexts(i.start(), empties);
 2878             }
 2879         }
 2880 
 2881         col->setSuppressDataChangedSignal(false);
 2882         col->setChanged();
 2883     }
 2884     m_spreadsheet->endMacro();
 2885     RESET_CURSOR;
 2886 
 2887     //selected rows were deleted but the view selection is still in place -> reset the selection in the view
 2888     m_tableView->clearSelection();
 2889 }
 2890 
 2891 void SpreadsheetView::clearSelectedCells() {
 2892     int first = firstSelectedRow();
 2893     int last = lastSelectedRow();
 2894     if (first < 0) return;
 2895 
 2896     //don't try to clear values if the selected cells don't have any values at all
 2897     bool empty = true;
 2898     for (auto* column : selectedColumns()) {
 2899         for (int row = last; row >= first; row--) {
 2900             if (column->isValid(row)) {
 2901                 empty = false;
 2902                 break;
 2903             }
 2904         }
 2905         if (!empty)
 2906             break;
 2907     }
 2908 
 2909     if (empty)
 2910         return;
 2911 
 2912     WAIT_CURSOR;
 2913     m_spreadsheet->beginMacro(i18n("%1: clear selected cells", m_spreadsheet->name()));
 2914     for (auto* column : selectedColumns()) {
 2915         column->setSuppressDataChangedSignal(true);
 2916 //      if (formulaModeActive()) {
 2917 //          int col = m_spreadsheet->indexOfChild<Column>(column);
 2918 //          for (int row = last; row >= first; row--)
 2919 //              if (isCellSelected(row, col))
 2920 //                  column->setFormula(row, QString());
 2921 //      } else {
 2922         int index = m_spreadsheet->indexOfChild<Column>(column);
 2923         if (isColumnSelected(index, true)) {
 2924             //if the whole column is selected, clear directly instead of looping over the rows
 2925             column->clear();
 2926         } else {
 2927             for (int row = last; row >= first; row--) {
 2928                 if (isCellSelected(row, index)) {
 2929                     if (row < column->rowCount())
 2930                         column->asStringColumn()->setTextAt(row, QString());
 2931                 }
 2932             }
 2933         }
 2934 
 2935         column->setSuppressDataChangedSignal(false);
 2936         column->setChanged();
 2937     }
 2938     m_spreadsheet->endMacro();
 2939     RESET_CURSOR;
 2940 }
 2941 
 2942 void SpreadsheetView::goToCell() {
 2943     auto* dlg = new GoToDialog(this);
 2944     if (dlg->exec() == QDialog::Accepted) {
 2945         int row = dlg->row();
 2946         if (row < 1)
 2947             row = 1;
 2948         if (row > m_spreadsheet->rowCount())
 2949             row = m_spreadsheet->rowCount();
 2950 
 2951         int col = dlg->column();
 2952         if (col < 1)
 2953             col = 1;
 2954         if (col > m_spreadsheet->columnCount())
 2955             col = m_spreadsheet->columnCount();
 2956 
 2957         goToCell(row-1, col-1);
 2958     }
 2959     delete dlg;
 2960 }
 2961 
 2962 //! Open the sort dialog for the given columns
 2963 void SpreadsheetView::sortDialog(const QVector<Column*>& cols) {
 2964     if (cols.isEmpty()) return;
 2965 
 2966     for (auto* col : cols)
 2967         col->setSuppressDataChangedSignal(true);
 2968 
 2969     auto* dlg = new SortDialog();
 2970     connect(dlg, &SortDialog::sort, m_spreadsheet, &Spreadsheet::sortColumns);
 2971     dlg->setColumns(cols);
 2972     int rc = dlg->exec();
 2973 
 2974     for (auto* col : cols) {
 2975         col->setSuppressDataChangedSignal(false);
 2976         if (rc == QDialog::Accepted)
 2977             col->setChanged();
 2978     }
 2979 }
 2980 
 2981 void SpreadsheetView::sortColumnAscending() {
 2982     QVector<Column*> cols = selectedColumns();
 2983     for (auto* col : cols)
 2984         col->setSuppressDataChangedSignal(true);
 2985     m_spreadsheet->sortColumns(cols.first(), cols, true);
 2986     for (auto* col : cols) {
 2987         col->setSuppressDataChangedSignal(false);
 2988         col->setChanged();
 2989     }
 2990 }
 2991 
 2992 void SpreadsheetView::sortColumnDescending() {
 2993     QVector<Column*> cols = selectedColumns();
 2994     for (auto* col : cols)
 2995         col->setSuppressDataChangedSignal(true);
 2996     m_spreadsheet->sortColumns(cols.first(), cols, false);
 2997     for (auto* col : cols) {
 2998         col->setSuppressDataChangedSignal(false);
 2999         col->setChanged();
 3000     }
 3001 }
 3002 
 3003 /*!
 3004   Cause a repaint of the header.
 3005 */
 3006 void SpreadsheetView::updateHeaderGeometry(Qt::Orientation o, int first, int last) {
 3007     Q_UNUSED(first)
 3008     Q_UNUSED(last)
 3009     //TODO
 3010     if (o != Qt::Horizontal) return;
 3011     m_tableView->horizontalHeader()->setStretchLastSection(true);  // ugly hack (flaw in Qt? Does anyone know a better way?)
 3012     m_tableView->horizontalHeader()->updateGeometry();
 3013     m_tableView->horizontalHeader()->setStretchLastSection(false); // ugly hack part 2
 3014 }
 3015 
 3016 /*!
 3017   selects the column \c column in the speadsheet view .
 3018 */
 3019 void SpreadsheetView::selectColumn(int column) {
 3020     const auto& index = m_model->index(0, column);
 3021     m_tableView->scrollTo(index);
 3022     QItemSelection selection(index, m_model->index(m_spreadsheet->rowCount()-1, column) );
 3023     m_suppressSelectionChangedEvent = true;
 3024     m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select);
 3025     m_suppressSelectionChangedEvent = false;
 3026 }
 3027 
 3028 /*!
 3029   deselects the column \c column in the speadsheet view .
 3030 */
 3031 void SpreadsheetView::deselectColumn(int column) {
 3032     QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount()-1, column) );
 3033     m_suppressSelectionChangedEvent = true;
 3034     m_tableView->selectionModel()->select(selection, QItemSelectionModel::Deselect);
 3035     m_suppressSelectionChangedEvent = false;
 3036 }
 3037 
 3038 /*!
 3039   called when a column in the speadsheet view was clicked (click in the header).
 3040   Propagates the selection of the column to the \c Spreadsheet object
 3041   (a click in the header always selects the column).
 3042 */
 3043 void SpreadsheetView::columnClicked(int column) {
 3044     m_spreadsheet->setColumnSelectedInView(column, true);
 3045 }
 3046 
 3047 /*!
 3048   called on selections changes. Propagates the selection/deselection of columns to the \c Spreadsheet object.
 3049 */
 3050 void SpreadsheetView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
 3051     Q_UNUSED(selected);
 3052     Q_UNUSED(deselected);
 3053 
 3054     if (m_suppressSelectionChangedEvent)
 3055         return;
 3056 
 3057     QItemSelectionModel* selModel = m_tableView->selectionModel();
 3058     for (int i = 0; i < m_spreadsheet->columnCount(); i++)
 3059         m_spreadsheet->setColumnSelectedInView(i, selModel->isColumnSelected(i, QModelIndex()));
 3060 }
 3061 
 3062 bool SpreadsheetView::exportView() {
 3063     auto* dlg = new ExportSpreadsheetDialog(this);
 3064     dlg->setFileName(m_spreadsheet->name());
 3065 
 3066     dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table"));
 3067     for (int i = 0; i < m_spreadsheet->columnCount(); ++i) {
 3068         if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::ColumnMode::Numeric) {
 3069             dlg->setExportToImage(false);
 3070             break;
 3071         }
 3072     }
 3073     if (selectedColumnCount() == 0)
 3074         dlg->setExportSelection(false);
 3075 
 3076     bool ret;
 3077     if ((ret = dlg->exec()) == QDialog::Accepted) {
 3078         const QString path = dlg->path();
 3079         const bool exportHeader = dlg->exportHeader();
 3080         WAIT_CURSOR;
 3081         switch (dlg->format()) {
 3082         case ExportSpreadsheetDialog::Format::ASCII: {
 3083             const QString separator = dlg->separator();
 3084             const QLocale::Language format = dlg->numberFormat();
 3085             exportToFile(path, exportHeader, separator, format);
 3086             break;
 3087         }
 3088         case ExportSpreadsheetDialog::Format::Binary:
 3089             break;
 3090         case ExportSpreadsheetDialog::Format::LaTeX: {
 3091             const bool exportLatexHeader = dlg->exportLatexHeader();
 3092             const bool gridLines = dlg->gridLines();
 3093             const bool captions = dlg->captions();
 3094             const bool skipEmptyRows = dlg->skipEmptyRows();
 3095             const bool exportEntire = dlg->entireSpreadheet();
 3096             exportToLaTeX(path, exportHeader, gridLines, captions,
 3097                 exportLatexHeader, skipEmptyRows, exportEntire);
 3098             break;
 3099         }
 3100         case ExportSpreadsheetDialog::Format::FITS: {
 3101             const int exportTo = dlg->exportToFits();
 3102             const bool commentsAsUnits = dlg->commentsAsUnitsFits();
 3103             exportToFits(path, exportTo, commentsAsUnits);
 3104             break;
 3105         }
 3106         case ExportSpreadsheetDialog::Format::SQLite:
 3107             exportToSQLite(path);
 3108             break;
 3109         }
 3110         RESET_CURSOR;
 3111     }
 3112     delete dlg;
 3113 
 3114     return ret;
 3115 }
 3116 
 3117 bool SpreadsheetView::printView() {
 3118     QPrinter printer;
 3119     auto* dlg = new QPrintDialog(&printer, this);
 3120     dlg->setWindowTitle(i18nc("@title:window", "Print Spreadsheet"));
 3121 
 3122     bool ret;
 3123     if ((ret = dlg->exec()) == QDialog::Accepted) {
 3124         print(&printer);
 3125     }
 3126     delete dlg;
 3127     return ret;
 3128 }
 3129 
 3130 bool SpreadsheetView::printPreview() {
 3131     auto* dlg = new QPrintPreviewDialog(this);
 3132     connect(dlg, &QPrintPreviewDialog::paintRequested, this, &SpreadsheetView::print);
 3133     return dlg->exec();
 3134 }
 3135 
 3136 /*!
 3137   prints the complete spreadsheet to \c printer.
 3138  */
 3139 void SpreadsheetView::print(QPrinter* printer) const {
 3140     WAIT_CURSOR;
 3141     QPainter painter (printer);
 3142 
 3143     const int dpiy = printer->logicalDpiY();
 3144     const int margin = (int) ( (1/2.54)*dpiy ); // 1 cm margins
 3145 
 3146     QHeaderView *hHeader = m_tableView->horizontalHeader();
 3147     QHeaderView *vHeader = m_tableView->verticalHeader();
 3148 
 3149     const int rows = m_spreadsheet->rowCount();
 3150     const int cols = m_spreadsheet->columnCount();
 3151     int height = margin;
 3152     const int vertHeaderWidth = vHeader->width();
 3153 
 3154     int columnsPerTable = 0;
 3155     int headerStringWidth = 0;
 3156     int firstRowStringWidth = 0;
 3157     bool tablesNeeded = false;
 3158     for (int col = 0; col < cols; ++col) {
 3159         headerStringWidth += m_tableView->columnWidth(col);
 3160         firstRowStringWidth += m_spreadsheet->column(col)->asStringColumn()->textAt(0).length();
 3161         if ((headerStringWidth >= printer->pageRect().width() -2*margin) ||
 3162                 (firstRowStringWidth >= printer->pageRect().width() - 2*margin)) {
 3163             tablesNeeded = true;
 3164             break;
 3165         }
 3166         columnsPerTable++;
 3167     }
 3168 
 3169     int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0;
 3170     const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols;
 3171 
 3172     if (!tablesNeeded) {
 3173         tablesCount = 1;
 3174         columnsPerTable = cols;
 3175     }
 3176 
 3177     if (remainingColumns > 0)
 3178         tablesCount++;
 3179     //Paint the horizontal header first
 3180     for (int table = 0; table < tablesCount; ++table) {
 3181         int right = margin + vertHeaderWidth;
 3182 
 3183         painter.setFont(hHeader->font());
 3184         QString headerString = m_tableView->model()->headerData(0, Qt::Horizontal).toString();
 3185         QRect br;
 3186         br = painter.boundingRect(br, Qt::AlignCenter, headerString);
 3187         QRect tr(br);
 3188         if (table != 0)
 3189             height += tr.height();
 3190         painter.drawLine(right, height, right, height+br.height());
 3191 
 3192         int i = table * columnsPerTable;
 3193         int toI = table * columnsPerTable + columnsPerTable;
 3194         if ((remainingColumns > 0) && (table == tablesCount-1)) {
 3195             i = (tablesCount-1)*columnsPerTable;
 3196             toI = (tablesCount-1)* columnsPerTable + remainingColumns;
 3197         }
 3198 
 3199         for (; i<toI; ++i) {
 3200             headerString = m_tableView->model()->headerData(i, Qt::Horizontal).toString();
 3201             const int w = m_tableView->columnWidth(i);
 3202             tr.setTopLeft(QPoint(right,height));
 3203             tr.setWidth(w);
 3204             tr.setHeight(br.height());
 3205 
 3206             painter.drawText(tr, Qt::AlignCenter, headerString);
 3207             right += w;
 3208             painter.drawLine(right, height, right, height+tr.height());
 3209 
 3210         }
 3211 
 3212         painter.drawLine(margin + vertHeaderWidth, height, right-1, height);//first horizontal line
 3213         height += tr.height();
 3214         painter.drawLine(margin, height, right-1, height);
 3215 
 3216         // print table values
 3217         QString cellText;
 3218         for (i = 0; i < rows; ++i) {
 3219             right = margin;
 3220             cellText = m_tableView->model()->headerData(i, Qt::Vertical).toString()+'\t';
 3221             tr = painter.boundingRect(tr, Qt::AlignCenter, cellText);
 3222             painter.drawLine(right, height, right, height+tr.height());
 3223 
 3224             br.setTopLeft(QPoint(right,height));
 3225             br.setWidth(vertHeaderWidth);
 3226             br.setHeight(tr.height());
 3227             painter.drawText(br, Qt::AlignCenter, cellText);
 3228             right += vertHeaderWidth;
 3229             painter.drawLine(right, height, right, height+tr.height());
 3230             int j = table * columnsPerTable;
 3231             int toJ = table * columnsPerTable + columnsPerTable;
 3232             if ((remainingColumns > 0) && (table == tablesCount-1)) {
 3233                 j = (tablesCount-1)*columnsPerTable;
 3234                 toJ = (tablesCount-1)* columnsPerTable + remainingColumns;
 3235             }
 3236             for (; j < toJ; j++) {
 3237                 int w = m_tableView->columnWidth(j);
 3238                 cellText = m_spreadsheet->column(j)->isValid(i) ? m_spreadsheet->text(i,j)+'\t':
 3239                         QLatin1String("- \t");
 3240                 tr = painter.boundingRect(tr,Qt::AlignCenter,cellText);
 3241                 br.setTopLeft(QPoint(right,height));
 3242                 br.setWidth(w);
 3243                 br.setHeight(tr.height());
 3244                 painter.drawText(br, Qt::AlignCenter, cellText);
 3245                 right += w;
 3246                 painter.drawLine(right, height, right, height+tr.height());
 3247 
 3248             }
 3249             height += br.height();
 3250             painter.drawLine(margin, height, right-1, height);
 3251 
 3252             if (height >= printer->height() - margin) {
 3253                 printer->newPage();
 3254                 height = margin;
 3255                 painter.drawLine(margin, height, right, height);
 3256             }
 3257         }
 3258     }
 3259     RESET_CURSOR;
 3260 }
 3261 
 3262 /*!
 3263  * the spreadsheet can have empty rows at the end full with NaNs.
 3264  * for the export we only need to export valid (non-empty) rows.
 3265  * this functions determines the maximal row to export, or -1
 3266  * if the spreadsheet doesn't have any data yet.
 3267 */
 3268 int SpreadsheetView::maxRowToExport() const {
 3269     int maxRow = -1;
 3270     for (int j = 0; j < m_spreadsheet->columnCount(); ++j) {
 3271         Column* col = m_spreadsheet->column(j);
 3272         auto mode = col->columnMode();
 3273         if (mode == AbstractColumn::ColumnMode::Numeric) {
 3274             for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
 3275                 if (!std::isnan(col->valueAt(i)) && i > maxRow)
 3276                     maxRow = i;
 3277             }
 3278         }
 3279         if (mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt) {
 3280             //TODO:
 3281             //integer column found. Since empty integer cells are equal to 0
 3282             //at the moment, we need to export the whole column.
 3283             //this logic needs to be adjusted once we're able to descriminate
 3284             //between empty and 0 values for integer columns
 3285             maxRow = m_spreadsheet->rowCount() - 1;
 3286             break;
 3287         } else if (mode == AbstractColumn::ColumnMode::DateTime) {
 3288             for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
 3289                 if (col->dateTimeAt(i).isValid() && i > maxRow)
 3290                     maxRow = i;
 3291             }
 3292         } else if (mode == AbstractColumn::ColumnMode::Text) {
 3293             for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
 3294                 if (!col->textAt(i).isEmpty() && i > maxRow)
 3295                     maxRow = i;
 3296             }
 3297         }
 3298     }
 3299 
 3300     return maxRow;
 3301 }
 3302 
 3303 void SpreadsheetView::exportToFile(const QString& path, const bool exportHeader, const QString& separator, QLocale::Language language) const {
 3304     QFile file(path);
 3305     if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
 3306         RESET_CURSOR;
 3307         QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path));
 3308         return;
 3309     }
 3310 
 3311     PERFTRACE("export spreadsheet to file");
 3312     QTextStream out(&file);
 3313 
 3314     int maxRow = maxRowToExport();
 3315     if (maxRow < 0)
 3316         return;
 3317 
 3318     const int cols = m_spreadsheet->columnCount();
 3319     QString sep = separator;
 3320     sep = sep.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive);
 3321     sep = sep.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive);
 3322 
 3323     //export header (column names)
 3324     if (exportHeader) {
 3325         for (int j = 0; j < cols; ++j) {
 3326             out << '"' << m_spreadsheet->column(j)->name() <<'"';
 3327             if (j != cols - 1)
 3328                 out << sep;
 3329         }
 3330         out << '\n';
 3331     }
 3332 
 3333     //export values
 3334     QLocale locale(language);
 3335     for (int i = 0; i <= maxRow; ++i) {
 3336         for (int j = 0; j < cols; ++j) {
 3337             Column* col = m_spreadsheet->column(j);
 3338             if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) {
 3339                 const Double2StringFilter* out_fltr = static_cast<Double2StringFilter*>(col->outputFilter());
 3340                 out << locale.toString(col->valueAt(i), out_fltr->numericFormat(), 16); // export with max. precision
 3341             } else
 3342                 out << col->asStringColumn()->textAt(i);
 3343 
 3344             if (j != cols - 1)
 3345                 out << sep;
 3346         }
 3347         out << '\n';
 3348     }
 3349 }
 3350 
 3351 void SpreadsheetView::exportToLaTeX(const QString & path, const bool exportHeaders,
 3352         const bool gridLines, const bool captions, const bool latexHeaders,
 3353         const bool skipEmptyRows, const bool exportEntire) const {
 3354     QFile file(path);
 3355     if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
 3356         RESET_CURSOR;
 3357         QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path));
 3358         return;
 3359     }
 3360 
 3361     QList<Column*> toExport;
 3362     int cols;
 3363     int totalRowCount = 0;
 3364     if (exportEntire) {
 3365         cols = const_cast<SpreadsheetView*>(this)->m_spreadsheet->columnCount();
 3366         totalRowCount = m_spreadsheet->rowCount();
 3367         for (int col = 0; col < cols; ++col)
 3368             toExport << m_spreadsheet->column(col);
 3369     } else {
 3370         cols = const_cast<SpreadsheetView*>(this)->selectedColumnCount();
 3371         const int firtsSelectedCol = const_cast<SpreadsheetView*>(this)->firstSelectedColumn();
 3372         bool rowsCalculated = false;
 3373         for (int col = firtsSelectedCol; col < firtsSelectedCol + cols; ++col) {
 3374             QVector<QString> textData;
 3375             for (int row = 0; row < m_spreadsheet->rowCount(); ++row) {
 3376                 if (const_cast<SpreadsheetView*>(this)->isRowSelected(row)) {
 3377                     textData << m_spreadsheet->column(col)->asStringColumn()->textAt(row);
 3378                     if (!rowsCalculated)
 3379                         totalRowCount++;
 3380                 }
 3381             }
 3382             if (!rowsCalculated)
 3383                 rowsCalculated = true;
 3384             Column* column = new Column(m_spreadsheet->column(col)->name(), textData);
 3385             toExport << column;
 3386         }
 3387     }
 3388     int columnsStringSize = 0;
 3389     int columnsPerTable = 0;
 3390 
 3391     for (int i = 0; i < cols; ++i) {
 3392         int maxSize = -1;
 3393         for (int j = 0; j < toExport.at(i)->asStringColumn()->rowCount(); ++j) {
 3394             if (toExport.at(i)->asStringColumn()->textAt(j).size() > maxSize)
 3395                 maxSize = toExport.at(i)->asStringColumn()->textAt(j).size();
 3396         }
 3397         columnsStringSize += maxSize;
 3398         if (!toExport.at(i)->isValid(0))
 3399             columnsStringSize += 3;
 3400         if (columnsStringSize > 65)
 3401             break;
 3402         ++columnsPerTable;
 3403     }
 3404 
 3405     const int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0;
 3406     const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols;
 3407 
 3408     bool columnsSeparating = (cols > columnsPerTable);
 3409     QTextStream out(&file);
 3410 
 3411     QProcess tex;
 3412     tex.start("latex", QStringList() << "--version", QProcess::ReadOnly);
 3413     tex.waitForFinished(500);
 3414     QString texVersionOutput = QString(tex.readAllStandardOutput());
 3415     texVersionOutput = texVersionOutput.split('\n')[0];
 3416 
 3417     int yearidx = -1;
 3418     for (int i = texVersionOutput.size() - 1; i >= 0; --i) {
 3419         if (texVersionOutput.at(i) == QChar('2')) {
 3420             yearidx = i;
 3421             break;
 3422         }
 3423     }
 3424 
 3425     if (texVersionOutput.at(yearidx+1) == QChar('/'))
 3426         yearidx -= 3;
 3427 
 3428     bool ok;
 3429     texVersionOutput.midRef(yearidx, 4).toInt(&ok);
 3430     int version = -1;
 3431     if (ok)
 3432         version = texVersionOutput.midRef(yearidx, 4).toInt(&ok);
 3433 
 3434     if (latexHeaders) {
 3435         out << QLatin1String("\\documentclass[11pt,a4paper]{article} \n");
 3436         out << QLatin1String("\\usepackage{geometry} \n");
 3437         out << QLatin1String("\\usepackage{xcolor,colortbl} \n");
 3438         if (version >= 2015)
 3439             out << QLatin1String("\\extrafloats{1280} \n");
 3440         out << QLatin1String("\\definecolor{HeaderBgColor}{rgb}{0.81,0.81,0.81} \n");
 3441         out << QLatin1String("\\geometry{ \n");
 3442         out << QLatin1String("a4paper, \n");
 3443         out << QLatin1String("total={170mm,257mm}, \n");
 3444         out << QLatin1String("left=10mm, \n");
 3445         out << QLatin1String("top=10mm } \n");
 3446 
 3447         out << QLatin1String("\\begin{document} \n");
 3448         out << QLatin1String("\\title{LabPlot Spreadsheet Export to \\LaTeX{} } \n");
 3449         out << QLatin1String("\\author{LabPlot} \n");
 3450         out << QLatin1String("\\date{\\today} \n");
 3451     }
 3452 
 3453     QString endTabularTable ("\\end{tabular} \n \\end{table} \n");
 3454     QString tableCaption ("\\caption{"+ m_spreadsheet->name()+ "} \n");
 3455     QString beginTable ("\\begin{table}[ht] \n");
 3456 
 3457     int rowCount = 0;
 3458     const int maxRows = 45;
 3459     bool captionRemoved = false;
 3460     if (columnsSeparating) {
 3461         QVector<int> emptyRowIndices;
 3462         for (int table = 0; table < tablesCount; ++table) {
 3463             QStringList textable;
 3464             captionRemoved = false;
 3465             textable << beginTable;
 3466 
 3467             if (captions)
 3468                 textable << tableCaption;
 3469             textable << QLatin1String("\\centering \n");
 3470             textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
 3471             for (int i = 0; i < columnsPerTable; ++i)
 3472                 textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") );
 3473             textable << QLatin1String("} \n");
 3474             if (gridLines)
 3475                 textable << QLatin1String("\\hline \n");
 3476 
 3477             if (exportHeaders) {
 3478                 if (latexHeaders)
 3479                     textable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
 3480                 for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) {
 3481                     textable << toExport.at(col)->name();
 3482                     if (col != ((table * columnsPerTable)+ columnsPerTable)-1)
 3483                         textable << QLatin1String(" & ");
 3484                 }
 3485                 textable << QLatin1String("\\\\ \n");
 3486                 if (gridLines)
 3487                     textable << QLatin1String("\\hline \n");
 3488             }
 3489             for (const auto& s : textable)
 3490                 out << s;
 3491 
 3492             QStringList values;
 3493             for (int row = 0; row < totalRowCount; ++row) {
 3494                 values.clear();
 3495                 bool notEmpty = false;
 3496                 for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col ) {
 3497                     if (toExport.at(col)->isValid(row)) {
 3498                         notEmpty = true;
 3499                         values << toExport.at(col)->asStringColumn()->textAt(row);
 3500                     } else
 3501                         values << QLatin1String("-");
 3502                     if (col != ((table * columnsPerTable)+ columnsPerTable)-1)
 3503                         values << QLatin1String(" & ");
 3504                 }
 3505                 if (!notEmpty && skipEmptyRows) {
 3506                     if (!emptyRowIndices.contains(row))
 3507                         emptyRowIndices << row;
 3508                 }
 3509                 if (emptyRowIndices.contains(row) && notEmpty)
 3510                     emptyRowIndices.remove(emptyRowIndices.indexOf(row));
 3511 
 3512                 if (notEmpty || !skipEmptyRows) {
 3513                     for (const auto& s : values)
 3514                         out << s;
 3515                     out << QLatin1String("\\\\ \n");
 3516                     if (gridLines)
 3517                         out << QLatin1String("\\hline \n");
 3518                     rowCount++;
 3519                     if (rowCount == maxRows) {
 3520                         out << endTabularTable;
 3521                         out << QLatin1String("\\clearpage \n");
 3522 
 3523                         if (captions)
 3524                             if (!captionRemoved)
 3525                                 textable.removeAt(1);
 3526                         for (const auto& s : textable)
 3527                             out << s;
 3528                         rowCount = 0;
 3529                         if (!captionRemoved)
 3530                             captionRemoved = true;
 3531                     }
 3532                 }
 3533             }
 3534             out << endTabularTable;
 3535         }
 3536 
 3537         //new table for the remaining columns
 3538         QStringList remainingTable;
 3539         remainingTable << beginTable;
 3540         if (captions)
 3541             remainingTable << tableCaption;
 3542         remainingTable << QLatin1String("\\centering \n");
 3543         remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
 3544         for (int c = 0; c < remainingColumns; ++c)
 3545             remainingTable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") );
 3546         remainingTable << QLatin1String("} \n");
 3547         if (gridLines)
 3548             remainingTable << QLatin1String("\\hline \n");
 3549         if (exportHeaders) {
 3550             if (latexHeaders)
 3551                 remainingTable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
 3552             for (int col = 0; col < remainingColumns; ++col) {
 3553                 remainingTable << toExport.at(col + (tablesCount * columnsPerTable))->name();
 3554                 if (col != remainingColumns-1)
 3555                     remainingTable << QLatin1String(" & ");
 3556             }
 3557             remainingTable << QLatin1String("\\\\ \n");
 3558             if (gridLines)
 3559                 remainingTable << QLatin1String("\\hline \n");
 3560         }
 3561 
 3562         for (const auto& s : remainingTable)
 3563             out << s;
 3564 
 3565         QStringList values;
 3566         captionRemoved = false;
 3567         for (int row = 0; row < totalRowCount; ++row) {
 3568             values.clear();
 3569             bool notEmpty = false;
 3570             for (int col = 0; col < remainingColumns; ++col ) {
 3571                 if (toExport.at(col + (tablesCount * columnsPerTable))->isValid(row)) {
 3572                     notEmpty = true;
 3573                     values << toExport.at(col + (tablesCount * columnsPerTable))->asStringColumn()->textAt(row);
 3574                 } else
 3575                     values << QLatin1String("-");
 3576                 if (col != remainingColumns-1)
 3577                     values << QLatin1String(" & ");
 3578             }
 3579             if (!emptyRowIndices.contains(row) && !notEmpty)
 3580                 notEmpty = true;
 3581             if (notEmpty || !skipEmptyRows) {
 3582                 for (const auto& s : values)
 3583                     out << s;
 3584                 out << QLatin1String("\\\\ \n");
 3585                 if (gridLines)
 3586                     out << QLatin1String("\\hline \n");
 3587                 rowCount++;
 3588                 if (rowCount == maxRows) {
 3589                     out << endTabularTable;
 3590                     out << QLatin1String("\\pagebreak[4] \n");
 3591                     if (captions)
 3592                         if (!captionRemoved)
 3593                             remainingTable.removeAt(1);
 3594                     for (const auto& s : remainingTable)
 3595                         out << s;
 3596                     rowCount = 0;
 3597                     if (!captionRemoved)
 3598                         captionRemoved = true;
 3599                 }
 3600             }
 3601         }
 3602         out << endTabularTable;
 3603     } else {
 3604         QStringList textable;
 3605         textable << beginTable;
 3606         if (captions)
 3607             textable << tableCaption;
 3608         textable << QLatin1String("\\centering \n");
 3609         textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
 3610         for (int c = 0; c < cols; ++c)
 3611             textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") );
 3612         textable << QLatin1String("} \n");
 3613         if (gridLines)
 3614             textable << QLatin1String("\\hline \n");
 3615         if (exportHeaders) {
 3616             if (latexHeaders)
 3617                 textable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
 3618             for (int col = 0; col < cols; ++col) {
 3619                 textable << toExport.at(col)->name();
 3620                 if (col != cols-1)
 3621                     textable << QLatin1String(" & ");
 3622             }
 3623             textable << QLatin1String("\\\\ \n");
 3624             if (gridLines)
 3625                 textable << QLatin1String("\\hline \n");
 3626         }
 3627 
 3628         for (const auto& s : textable)
 3629             out << s;
 3630         QStringList values;
 3631         captionRemoved = false;
 3632         for (int row = 0; row < totalRowCount; ++row) {
 3633             values.clear();
 3634             bool notEmpty = false;
 3635 
 3636             for (int col = 0; col < cols; ++col ) {
 3637                 if (toExport.at(col)->isValid(row)) {
 3638                     notEmpty = true;
 3639                     values << toExport.at(col)->asStringColumn()->textAt(row);
 3640                 } else
 3641                     values << "-";
 3642                 if (col != cols-1)
 3643                     values << " & ";
 3644             }
 3645 
 3646             if (notEmpty || !skipEmptyRows) {
 3647                 for (const auto& s : values)
 3648                     out << s;
 3649                 out << QLatin1String("\\\\ \n");
 3650                 if (gridLines)
 3651                     out << QLatin1String("\\hline \n");
 3652                 rowCount++;
 3653                 if (rowCount == maxRows) {
 3654                     out << endTabularTable;
 3655                     out << QLatin1String("\\clearpage \n");
 3656                     if (captions)
 3657                         if (!captionRemoved)
 3658                             textable.removeAt(1);
 3659                     for (const auto& s : textable)
 3660                         out << s;
 3661                     rowCount = 0;
 3662                     if (!captionRemoved)
 3663                         captionRemoved = true;
 3664                 }
 3665             }
 3666         }
 3667         out << endTabularTable;
 3668     }
 3669     if (latexHeaders)
 3670         out << QLatin1String("\\end{document} \n");
 3671 
 3672     if (!exportEntire) {
 3673         qDeleteAll(toExport);
 3674         toExport.clear();
 3675     } else
 3676         toExport.clear();
 3677 }
 3678 
 3679 void SpreadsheetView::exportToFits(const QString &fileName, const int exportTo, const bool commentsAsUnits) const {
 3680     auto* filter = new FITSFilter;
 3681 
 3682     filter->setExportTo(exportTo);
 3683     filter->setCommentsAsUnits(commentsAsUnits);
 3684     filter->write(fileName, m_spreadsheet);
 3685 
 3686     delete filter;
 3687 }
 3688 
 3689 void SpreadsheetView::exportToSQLite(const QString& path) const {
 3690     QFile file(path);
 3691     if (!file.open(QFile::WriteOnly | QFile::Truncate))
 3692         return;
 3693 
 3694     PERFTRACE("export spreadsheet to SQLite database");
 3695     QApplication::processEvents(QEventLoop::AllEvents, 0);
 3696 
 3697     //create database
 3698     const QStringList& drivers = QSqlDatabase::drivers();
 3699     QString driver;
 3700     if (drivers.contains(QLatin1String("QSQLITE3")))
 3701         driver = QLatin1String("QSQLITE3");
 3702     else
 3703         driver = QLatin1String("QSQLITE");
 3704 
 3705     QSqlDatabase db = QSqlDatabase::addDatabase(driver);
 3706     db.setDatabaseName(path);
 3707     if (!db.open()) {
 3708         RESET_CURSOR;
 3709         KMessageBox::error(nullptr, i18n("Couldn't create the SQLite database %1.", path));
 3710     }
 3711 
 3712     //create table
 3713     const int cols = m_spreadsheet->columnCount();
 3714     QString query = QLatin1String("create table ") + m_spreadsheet->name() + QLatin1String(" (");
 3715     for (int i = 0; i < cols; ++i) {
 3716         Column* col = m_spreadsheet->column(i);
 3717         if (i != 0)
 3718             query += QLatin1String(", ");
 3719 
 3720         query += QLatin1String("\"") + col->name() + QLatin1String("\" ");
 3721         switch (col->columnMode()) {
 3722         case AbstractColumn::ColumnMode::Numeric:
 3723             query += QLatin1String("REAL");
 3724             break;
 3725         case AbstractColumn::ColumnMode::Integer:
 3726         case AbstractColumn::ColumnMode::BigInt:
 3727             query += QLatin1String("INTEGER");
 3728             break;
 3729         case AbstractColumn::ColumnMode::Text:
 3730         case AbstractColumn::ColumnMode::Month:
 3731         case AbstractColumn::ColumnMode::Day:
 3732         case AbstractColumn::ColumnMode::DateTime:
 3733             query += QLatin1String("TEXT");
 3734             break;
 3735         }
 3736     }
 3737     query += QLatin1Char(')');
 3738     QSqlQuery q;
 3739     if (!q.exec(query)) {
 3740         RESET_CURSOR;
 3741         KMessageBox::error(nullptr, i18n("Failed to create table in the SQLite database %1.", path) + '\n' + q.lastError().databaseText());
 3742         db.close();
 3743         return;
 3744     }
 3745 
 3746 
 3747     int maxRow = maxRowToExport();
 3748     if (maxRow < 0) {
 3749         db.close();
 3750         return;
 3751     }
 3752 
 3753     //create bulk insert statement
 3754     {
 3755     PERFTRACE("Create the bulk insert statement");
 3756     q.exec(QLatin1String("BEGIN TRANSACTION;"));
 3757     query = "INSERT INTO '" + m_spreadsheet->name() + "' (";
 3758     for (int i = 0; i < cols; ++i) {
 3759         if (i != 0)
 3760             query += QLatin1String(", ");
 3761         query += QLatin1Char('\'') + m_spreadsheet->column(i)->name() + QLatin1Char('\'');
 3762     }
 3763     query += QLatin1String(") VALUES ");
 3764 
 3765     for (int i = 0; i <= maxRow; ++i) {
 3766         if (i != 0)
 3767             query += QLatin1String(",");
 3768 
 3769         query += QLatin1Char('(');
 3770         for (int j = 0; j < cols; ++j) {
 3771             Column* col = m_spreadsheet->column(j);
 3772             if (j != 0)
 3773                 query += QLatin1String(", ");
 3774 
 3775             query += QLatin1Char('\'') + col->asStringColumn()->textAt(i) + QLatin1Char('\'');
 3776         }
 3777         query += QLatin1String(")");
 3778     }
 3779     query += QLatin1Char(';');
 3780     }
 3781 
 3782     //insert values
 3783     if (!q.exec(query)) {
 3784         RESET_CURSOR;
 3785         KMessageBox::error(nullptr, i18n("Failed to insert values into the table."));
 3786         QDEBUG(Q_FUNC_INFO << ", bulk insert error " << q.lastError().databaseText());
 3787     } else
 3788         q.exec(QLatin1String("COMMIT TRANSACTION;"));
 3789 
 3790     //close the database
 3791     db.close();
 3792 }