labplot  2.8.2
About: LabPlot is an application for plotting and analysis of 2D and 3D functions and data. It is a complete rewrite of LabPlot1 and lacks in the first release a lot of features available in the predecessor. On the other hand, the GUI and the usability is more superior.
  Fossies Dox: labplot-2.8.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

SpreadsheetView.cpp
Go to the documentation of this file.
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"
38 #include "backend/lib/macros.h"
39 #include "backend/lib/trace.h"
47 
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 
103 
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 
147  delete m_model;
148 }
149 
151  initActions();
152  initMenus();
153 
154  m_tableView->setModel(m_model);
155  auto* delegate = new SpreadsheetItemDelegate(this);
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
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);
201 
202  for (auto* column : m_spreadsheet->children<Column>())
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 
213 }
214 
215 /*!
216  set the column sizes to the saved values or resize to content if no size was saved yet
217 */
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 
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);
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);
280 
281  action_set_as_xerr_minus = new QAction(i18n("X-error minus"), this);
283 
284  action_set_as_xerr_plus = new QAction(i18n("X-error plus"), this);
286 
287  action_set_as_yerr = new QAction(i18n("Y-error"), this);
289 
290  action_set_as_yerr_minus = new QAction(i18n("Y-error minus"), this);
292 
293  action_set_as_yerr_plus = new QAction(i18n("Y-error plus"), this);
295 
296  //data manipulation
297  action_add_value = new QAction(i18n("Add Value"), this);
299  action_subtract_value = new QAction(i18n("Subtract Value"), this);
301  action_multiply_value = new QAction(i18n("Multiply by Value"), this);
303  action_divide_value = new QAction(i18n("Divide by Value"), this);
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);
403  action_plot_data_histogram = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this);
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);
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);
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);
416  addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this);
418  addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this);
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);
467 }
468 
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();
482  }
483 
485 
486  if (!m_readOnly) {
489  m_selectionMenu->addSeparator();
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);
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();
529  m_analyzePlotMenu->addSeparator();
532  m_analyzePlotMenu->addSeparator();
534  m_analyzePlotMenu->addSeparator();
537 
538  m_columnSetAsMenu = new QMenu(i18n("Set Column As"), this);
539  m_columnMenu->addSeparator();
543  m_columnSetAsMenu->addSeparator();
547  m_columnSetAsMenu->addSeparator();
551  m_columnSetAsMenu->addSeparator();
554 
555  if (!m_readOnly) {
556  m_columnGenerateDataMenu = new QMenu(i18n("Generate Data"), this);
562  m_columnMenu->addSeparator();
564  m_columnMenu->addSeparator();
565 
566  m_columnManipulateDataMenu = new QMenu(i18n("Manipulate Data"), this);
571  m_columnManipulateDataMenu->addSeparator();
573  m_columnManipulateDataMenu->addSeparator();
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));
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();
634 
636  m_columnMenu->addSeparator();
637 
638  m_columnSortMenu = new QMenu(i18n("Sort"), this);
639  m_columnSortMenu->setIcon(QIcon::fromTheme("view-sort-ascending"));
643  m_columnMenu->addSeparator();
644  m_columnMenu->addMenu(m_columnSortMenu);
645  m_columnMenu->addSeparator();
646 
649  m_columnMenu->addSeparator();
652  m_columnMenu->addSeparator();
655  }
656  m_columnMenu->addSeparator();
658  m_columnMenu->addSeparator();
659 
661 
662  //Spreadsheet menu
663  m_spreadsheetMenu = new QMenu(this);
666  m_spreadsheetMenu->addSeparator();
668  m_spreadsheetMenu->addSeparator();
670  if (!m_readOnly) {
674  }
675  m_spreadsheetMenu->addSeparator();
677  m_spreadsheetMenu->addSeparator();
679  m_spreadsheetMenu->addSeparator();
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 
693  m_rowMenu->addSeparator();
694 
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 
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  */
826  Q_ASSERT(menu);
827 
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  */
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
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);
919 }
920 
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
960  Q_UNUSED(index);
961 }
962 
963 /*!
964  Returns whether comments are shown currently or not
965 */
968 }
969 
970 /*!
971  toggles the column comment in the horizontal header
972 */
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
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 */
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  */
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  */
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 */
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  */
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  */
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  */
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) {
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) {
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))
1252  else if (key_event->key() == Qt::Key_Backspace || key_event->matches(QKeySequence::Delete))
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)
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 */
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())
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  */
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 
1361  return m_model->formulaModeActive();
1362 }
1363 
1366 }
1367 
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 
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 
1392  if (firstSelectedRow() < 0)
1393  return;
1394 
1395  WAIT_CURSOR;
1396  m_spreadsheet->beginMacro(i18n("%1: cut selected cells", m_spreadsheet->name()));
1397  copySelection();
1400  RESET_CURSOR;
1401 }
1402 
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 
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
1437  output_str += numberLocale.toString(col_ptr->valueAt(first_row + r), formats.at(c), 16); // copy with max. precision
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 
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 
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);
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);
1575  auto* filter = static_cast<DateTime2StringFilter*>(new_col->outputFilter());
1576  filter->setFormat(AbstractFileFilter::dateTimeFormat(nonEmptyValue));
1577  }
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);
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 
1665  RESET_CURSOR;
1666 }
1667 
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 
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 
1699  RESET_CURSOR;
1700 }
1701 
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 
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 
1733  RESET_CURSOR;
1734 }
1735 
1737  const QAction* action = dynamic_cast<const QAction*>(QObject::sender());
1740  type = (PlotDataDialog::PlotType)action->data().toInt();
1741 
1742  auto* dlg = new PlotDataDialog(m_spreadsheet, type);
1743 
1746  dlg->setAnalysisAction(type);
1747  }
1748 
1749  dlg->exec();
1750 }
1751 
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()) {
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  }
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  }
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  }
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
1808  break;
1809  }
1810 
1811  col_ptr->setSuppressDataChangedSignal(false);
1812  col_ptr->setChanged();
1813  }
1815  RESET_CURSOR;
1816 }
1817 
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()) {
1836  col->replaceInteger(0, int_data);
1837  break;
1840  col->setColumnMode(AbstractColumn::ColumnMode::Integer);
1841  col->replaceInteger(0, int_data);
1842  break;
1847  break;
1848  }
1849  }
1850 
1852  RESET_CURSOR;
1853 }
1854 
1855 //TODO: this function is not used currently.
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()) {
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  }
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  }
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  }
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  }
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  }
1950  RESET_CURSOR;
1951 }
1952 
1954  if (selectedColumnCount() < 1) return;
1955  auto* dlg = new RandomValuesDialog(m_spreadsheet);
1956  dlg->setColumns(selectedColumns());
1957  dlg->exec();
1958 }
1959 
1961  if (selectedColumnCount() < 1) return;
1962  auto* dlg = new EquidistantValuesDialog(m_spreadsheet);
1963  dlg->setColumns(selectedColumns());
1964  dlg->exec();
1965 }
1966 
1968  if (selectedColumnCount() < 1) return;
1969  auto* dlg = new FunctionValuesDialog(m_spreadsheet);
1970  dlg->setColumns(selectedColumns());
1971  dlg->exec();
1972 }
1973 
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()) {
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;
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;
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;
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
2068  break;
2069  }
2070 
2071  col_ptr->setSuppressDataChangedSignal(false);
2072  col_ptr->setChanged();
2073  }
2075 }
2076 
2077 /*!
2078  Open the sort dialog for all columns.
2079 */
2082 }
2083 
2084 /*!
2085  Insert an empty column left to the first selected column
2086 */
2088  insertColumnsLeft(1);
2089 }
2090 
2091 /*!
2092  Insert multiple empty columns left to the firt selected column
2093 */
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  */
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);
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);
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);
2148  newCol->insertRows(0, rows);
2149 
2150  //add/append a new column
2151  m_spreadsheet->addChild(newCol);
2152  }
2153  }
2154  }
2155 
2157  RESET_CURSOR;
2158 }
2159 
2160 /*!
2161  Insert an empty column right to the last selected column
2162 */
2164  insertColumnsRight(1);
2165 }
2166 
2167 /*!
2168  Insert multiple empty columns right to the last selected column
2169 */
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  */
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);
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);
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);
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);
2234  newCol->insertRows(0, rows);
2235 
2236  //add/append a new column
2237  m_spreadsheet->addChild(newCol);
2238  }
2239  }
2240  }
2241 
2243  RESET_CURSOR;
2244 }
2245 
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 
2254  RESET_CURSOR;
2255 }
2256 
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 
2277  RESET_CURSOR;
2278 }
2279 
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 
2296 }
2297 
2298 /*!
2299  * add, subtract, multiply, divide
2300  */
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 
2313  WAIT_CURSOR;
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  }
2347  RESET_CURSOR;
2348 }
2349 
2351  if (selectedColumnCount() < 1) return;
2352  auto* dlg = new DropValuesDialog(m_spreadsheet);
2353  dlg->setColumns(selectedColumns());
2354  dlg->exec();
2355 }
2356 
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 
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  }
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 
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 
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 */
2723 }
2724 
2726  showColumnStatistics(true);
2727 }
2728 
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 
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 */
2774  insertRowsAbove(1);
2775 }
2776 
2777 /*!
2778  Insert multiple empty rows above(=before) the first selected row
2779 */
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  */
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);
2803  RESET_CURSOR;
2804 }
2805 
2806 /*!
2807  Insert an empty row below the last selected row
2808 */
2810  insertRowsBelow(1);
2811 }
2812 
2813 /*!
2814  Insert an empty row below the last selected row
2815 */
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  */
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 
2844  RESET_CURSOR;
2845 }
2846 
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());
2856  RESET_CURSOR;
2857 }
2858 
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  }
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 
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  }
2939  RESET_CURSOR;
2940 }
2941 
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
2964  if (cols.isEmpty()) return;
2965 
2966  for (auto* col : cols)
2967  col->setSuppressDataChangedSignal(true);
2968 
2969  auto* dlg = new SortDialog();
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 
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 
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 */
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) );
3024  m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select);
3026 }
3027 
3028 /*!
3029  deselects the column \c column in the speadsheet view .
3030 */
3032  QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount()-1, column) );
3034  m_tableView->selectionModel()->select(selection, QItemSelectionModel::Deselect);
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 */
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 
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 
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) {
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()) {
3083  const QString separator = dlg->separator();
3084  const QLocale::Language format = dlg->numberFormat();
3085  exportToFile(path, exportHeader, separator, format);
3086  break;
3087  }
3089  break;
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  }
3101  const int exportTo = dlg->exportToFits();
3102  const bool commentsAsUnits = dlg->commentsAsUnitsFits();
3103  exportToFits(path, exportTo, commentsAsUnits);
3104  break;
3105  }
3107  exportToSQLite(path);
3108  break;
3109  }
3110  RESET_CURSOR;
3111  }
3112  delete dlg;
3113 
3114  return ret;
3115 }
3116 
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 
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 */
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();
3274  for (int i = 0; i < m_spreadsheet->rowCount(); ++i) {
3275  if (!std::isnan(col->valueAt(i)) && i > maxRow)
3276  maxRow = i;
3277  }
3278  }
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;
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);
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()) {
3723  query += QLatin1String("REAL");
3724  break;
3727  query += QLatin1String("INTEGER");
3728  break;
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 }
NormalizationMethod
@ DivideByMedian
@ DivideByCount
@ DivideBySD
@ DivideByMean
@ DivideByMin
@ DivideBySum
@ DivideByMax
@ DivideByMAD
@ DivideByRange
@ DivideByMode
@ Rescale
@ ZScoreMAD
@ ZScoreIQR
@ DivideByIQR
@ ZScoreSD
TukeyLadderPower
@ Inverse
@ SquareRoot
@ Cube
@ Squared
@ InverseSquareRoot
@ InverseSquared
Base class of all persistent objects in a Project.
void aspectAdded(const AbstractAspect *)
Emitted after a new Aspect has been added to the tree.
int indexOfChild(const AbstractAspect *child, ChildIndexFlags flags={}) const
Return (0 based) index of child in the list of children inheriting from class T.
void addChild(AbstractAspect *)
Add the given Aspect to my list of children.
void aspectAboutToBeRemoved(const AbstractAspect *)
Emitted before an aspect is removed from its parent.
QString name() const
T * child(int index, ChildIndexFlags flags={}) const
AbstractAspect * parentAspect() const
Return my parent Aspect or 0 if I currently don't have one.
void beginMacro(const QString &text)
Begin an undo stack macro (series of commands)
bool isLoading() const
QVector< AbstractAspect * > children(AspectType type, ChildIndexFlags flags={}) const
void insertChildBefore(AbstractAspect *child, AbstractAspect *before)
Insert the given Aspect at a specific position in my list of children.
void removeChild(AbstractAspect *)
Remove the given Aspect from my list of children.
void endMacro()
End the current undo stack macro.
void insertRows(int before, int count)
Insert some empty (or initialized with invalid values) rows.
bool isNumeric() const
void setMasked(const Interval< int > &i, bool mask=true)
Set an interval masked.
bool isValid(int row) const
Convenience method for mode-independent testing of validity.
static QString dateTimeFormat(const QString &valueString)
static AbstractColumn::ColumnMode columnMode(const QString &valueString, const QString &dateTimeFormat, QLocale::Language)
Dialog for adding/subtracting a value from column values.
QString textAt(int) const override
Return the content of row 'row'.
void setTextAt(int, const QString &) override
Set the content of row 'row'.
Aspect that manages a column.
Definition: Column.h:42
void setColumnMode(AbstractColumn::ColumnMode) override
Set the column mode.
Definition: Column.cpp:289
ColumnStringIO * asStringColumn() const
Return a wrapper column object used for String I/O.
Definition: Column.cpp:1451
void setValueAt(int, double) override
Set the content of row 'row'.
Definition: Column.cpp:579
QDateTime dateTimeAt(int) const override
Return the QDateTime in row 'row'.
Definition: Column.cpp:968
void replaceInteger(int, const QVector< int > &) override
Replace a range of values.
Definition: Column.cpp:607
void replaceBigInt(int, const QVector< qint64 > &) override
Replace a range of values.
Definition: Column.cpp:629
void replaceValues(int, const QVector< double > &) override
Replace a range of values.
Definition: Column.cpp:588
void setIntegerAt(int, int) override
Set the content of row 'row'.
Definition: Column.cpp:598
void setPlotDesignation(AbstractColumn::PlotDesignation) override
Set the column plot designation.
Definition: Column.cpp:403
void setChanged()
Definition: Column.cpp:998
void setSuppressDataChangedSignal(const bool)
Definition: Column.cpp:246
void clear() override
Clear the whole column.
Definition: Column.cpp:425
void requestProjectContextMenu(QMenu *)
AbstractColumn::ColumnMode columnMode() const override
Return the column mode.
Definition: Column.cpp:1388
AbstractSimpleFilter * outputFilter() const
Definition: Column.cpp:1444
QString textAt(int) const override
Return the content of row 'row'.
Definition: Column.cpp:941
void setWidth(const int)
Set width.
Definition: Column.cpp:418
void setBigIntAt(int, qint64) override
Set the content of row 'row'.
Definition: Column.cpp:617
AbstractColumn::PlotDesignation plotDesignation() const override
Return the column plot designation.
Definition: Column.cpp:1413
double valueAt(int) const override
Return the double value in row 'row'.
Definition: Column.cpp:975
int width() const
Get width.
Definition: Column.cpp:411
bool hasValues() const
Definition: Column.cpp:859
Conversion filter QDateTime -> QString.
void setFormat(const QString &format)
Set the format string to be used for conversion.
Locale-aware conversion filter double -> QString.
char numericFormat() const
Get format character as in QString::number.
Dialog for generating values from a mathematical function.
Dialog for equidistant values.
Dialog for exporting a spreadsheet to a file.
Manages the import/export of data from/to a FITS file.
Definition: FITSFilter.h:41
void setExportTo(const int)
Sets exportTo to exportTo.
Definition: FITSFilter.cpp:245
Dialog for generating values from a mathematical function.
Dialog to provide the cell coordinates to navigate to.
Definition: GoToDialog.h:34
A class representing an interval-based attribute (bool version)
void setValue(const Interval< int > &i, bool value=true)
Dialog for generating plots for the spreadsheet data.
Dialog for generating non-uniform random numbers.
Dialog to provide the rescale interval for the select columns in the spreadsheet.
Definition: RescaleDialog.h:35
Dialog for sorting the columns in a spreadsheet.
Definition: SortDialog.h:35
void sort(Column *, QVector< Column * >, bool ascending)
void showComments(bool on=true)
Item delegate for SpreadsheetView.
Model for the access to a Spreadsheet.
void activateFormulaMode(bool on)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
bool formulaModeActive() const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
View class for Spreadsheet.
QAction * action_mask_selection
QActionGroup * ladderOfPowersActionGroup
QAction * action_set_as_none
QAction * action_unmask_selection
QAction * action_set_as_xerr_minus
void exportToFits(const QString &path, const int exportTo, const bool commentsAsUnits) const
QAction * action_subtract_value
QAction * action_statistics_all_columns
QAction * action_set_as_xerr_plus
void exportToLaTeX(const QString &, const bool exportHeaders, const bool gridLines, const bool captions, const bool latexHeaders, const bool skipEmptyRows, const bool exportEntire) const
QAction * action_paste_into_selection
SpreadsheetModel * m_model
QMenu * m_columnNormalizeMenu
QMenu * m_columnGenerateDataMenu
QAction * action_mask_values
bool isRowSelected(int row, bool full=false) const
QAction * action_sort_desc_column
QMenu * m_columnManipulateDataMenu
void handleHorizontalHeaderDoubleClicked(int index)
void handleHorizontalSectionMoved(int index, int from, int to)
bool isColumnSelected(int col, bool full=false) const
void handleAspectAdded(const AbstractAspect *)
void fillWithEquidistantValues()
QAction * action_set_as_z
QAction * addInterpolationAction
void getCurrentCell(int *row, int *col) const
SpreadsheetView(Spreadsheet *spreadsheet, bool readOnly=false)
void showComments(bool on=true)
Shows (on=true) or hides (on=false) the column comments in the horizontal header.
QAction * action_copy_selection
void exportToFile(const QString &, const bool, const QString &, QLocale::Language) const
SpreadsheetHeaderView * m_horizontalHeader
void print(QPrinter *) const
QAction * action_statistics_rows
QAction * addDifferentiationAction
QAction * action_insert_row_below
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
QAction * action_clear_rows
QAction * action_insert_columns_left
void exportToSQLite(const QString &path) const
void sortDialog(const QVector< Column * > &)
Open the sort dialog for the given columns.
QAction * action_insert_columns_right
bool isCellSelected(int row, int col) const
QVector< Column * > selectedColumns(bool full=false) const
QAction * action_divide_value
~SpreadsheetView() override
void powerTransformSelectedColumns(QAction *)
QAction * action_remove_columns
QAction * action_set_as_x
void createColumnContextMenu(QMenu *)
QTableView * m_tableView
bool eventFilter(QObject *, QEvent *) override
QAction * action_plot_data_xycurve
QAction * addDataReductionAction
void fillSelectedCellsWithRowNumbers()
QAction * action_insert_rows_above
QAction * action_remove_rows
QAction * action_insert_column_right
void createContextMenu(QMenu *)
QAction * action_statistics_columns
IntervalAttribute< bool > selectedRows(bool full=false) const
Spreadsheet * m_spreadsheet
QAction * action_cut_selection
QAction * action_set_as_xerr
QAction * action_fill_random
void activateFormulaMode(bool on)
void setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select=true)
QMenu * m_columnSortMenu
QAction * addSmoothAction
int firstSelectedColumn(bool full=false) const
int selectedColumnCount(bool full=false) const
bool m_suppressSelectionChangedEvent
QMenu * m_columnLadderOfPowersMenu
QAction * action_plot_data_histogram
QAction * action_clear_selection
void handleHorizontalSectionResized(int logicalIndex, int oldSize, int newSize)
QAction * action_drop_values
QActionGroup * normalizeColumnActionGroup
void handleHeaderDataChanged(Qt::Orientation orientation, int first, int last)
void checkColumnMenus(bool numeric, bool datetime, bool hasValues)
QMenu * m_columnSetAsMenu
QAction * action_add_value
QAction * action_sort_spreadsheet
QAction * action_insert_rows_below
QAction * action_multiply_value
void updateHeaderGeometry(Qt::Orientation o, int first, int last)
QAction * action_sort_asc_column
QAction * action_fill_function
void currentColumnChanged(const QModelIndex &current, const QModelIndex &previous)
QAction * action_clear_columns
QAction * action_clear_masks
QAction * action_insert_row_above
bool areCommentsShown() const
QVector< QAction * > addFitAction
QAction * action_toggle_comments
QAction * action_sort_columns
bool formulaModeActive() const
QAction * action_set_as_yerr
QMenu * m_spreadsheetMenu
QAction * addIntegrationAction
int maxRowToExport() const
void handleAspectAboutToBeRemoved(const AbstractAspect *)
QAction * action_clear_spreadsheet
void fillSelectedCellsWithConstValues()
QAction * action_reverse_columns
QAction * action_select_all
QAction * action_go_to_cell
QAction * action_set_as_y
void showColumnStatistics(bool forAll=false)
void setCellSelected(int row, int col, bool select=true)
int lastSelectedColumn(bool full=false) const
int firstSelectedRow(bool full=false) const
QAction * action_set_as_yerr_plus
void fillToolBar(QToolBar *)
QAction * action_insert_column_left
QAction * action_fill_random_nonuniform
QAction * action_fill_equidistant
void normalizeSelectedColumns(QAction *)
QAction * action_set_as_yerr_minus
QAction * action_fill_const
void fillSelectedCellsWithRandomNumbers()
QAction * addFourierFilterAction
QAction * action_fill_row_numbers
int lastSelectedRow(bool full=false) const
QMenu * m_analyzePlotMenu
Aspect providing a spreadsheet table with column logic.
Definition: Spreadsheet.h:40
QString text(int row, int col) const
void requestProjectContextMenu(QMenu *)
int columnCount() const
int rowCount() const
void columnDeselected(int)
Column * column(int index) const
void appendRows(int)
void clearMasks()
void removeRows(int first, int count)
void insertRows(int before, int count)
void moveColumn(int from, int to)
void sortColumns(Column *leading, const QVector< Column * > &, bool ascending)
void setRowCount(int)
void setColumnSelectedInView(int index, bool selected)
void columnSelected(int)
#define WAIT_CURSOR
Definition: macros.h:63
#define RESET_CURSOR
Definition: macros.h:64
#define SET_NUMBER_LOCALE
Definition: macros.h:75
#define QDEBUG(x)
Definition: macros.h:47
#define i18n(m)
Definition: nsl_common.h:38
#define PERFTRACE(msg)
Definition: trace.h:57