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)  

Spreadsheet.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  File : Spreadsheet.cpp
3  Project : LabPlot
4  Description : Aspect providing a spreadsheet table with column logic
5  --------------------------------------------------------------------
6  Copyright : (C) 2006-2008 Tilman Benkert (thzs@gmx.net)
7  Copyright : (C) 2006-2009 Knut Franke (knut.franke@gmx.de)
8  Copyright : (C) 2012-2019 Alexander Semke (alexander.semke@web.de)
9  Copyright : (C) 2017-2020 Stefan Gerlach (stefan.gerlach@uni.kn)
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 #include "Spreadsheet.h"
31 #include "SpreadsheetModel.h"
38 
39 #include <QIcon>
40 
41 #include <KConfigGroup>
42 #include <KLocalizedString>
43 #include <KSharedConfig>
44 
45 #include <algorithm>
46 
47 /*!
48  \class Spreadsheet
49  \brief Aspect providing a spreadsheet table with column logic.
50 
51  Spreadsheet is a container object for columns with no data of its own. By definition, it's columns
52  are all of its children inheriting from class Column. Thus, the basic API is already defined
53  by AbstractAspect (managing the list of columns, notification of column insertion/removal)
54  and Column (changing and monitoring state of the actual data).
55 
56  Spreadsheet stores a pointer to its primary view of class SpreadsheetView. SpreadsheetView calls the Spreadsheet
57  API but Spreadsheet only notifies SpreadsheetView by signals without calling its API directly. This ensures a
58  maximum independence of UI and backend. SpreadsheetView can be easily replaced by a different class.
59  User interaction is completely handled in SpreadsheetView and translated into
60  Spreadsheet API calls (e.g., when a user edits a cell this will be handled by the delegate of
61  SpreadsheetView and Spreadsheet will not know whether a script or a user changed the data.). All actions,
62  menus etc. for the user interaction are handled SpreadsheetView, e.g., via a context menu.
63  Selections are also handled by SpreadsheetView. The view itself is created by the first call to view();
64 
65  \ingroup backend
66 */
67 
68 Spreadsheet::Spreadsheet(const QString& name, bool loading, AspectType type)
69  : AbstractDataSource(name, type) {
70 
71  if (!loading)
72  init();
73 }
74 
75 /*!
76  initializes the spreadsheet with the default number of columns and rows
77 */
79  KConfig config;
80  KConfigGroup group = config.group(QLatin1String("Spreadsheet"));
81 
82  const int columns = group.readEntry(QLatin1String("ColumnCount"), 2);
83  const int rows = group.readEntry(QLatin1String("RowCount"), 100);
84 
85  for (int i = 0; i < columns; i++) {
86  Column* new_col = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric);
88  addChild(new_col);
89  }
90  setRowCount(rows);
91 }
92 
94  m_model = model;
95 }
96 
98  return m_model;
99 }
100 
101 /*! Constructs a primary view on me.
102  This method may be called multiple times during the life time of an Aspect, or it might not get
103  called at all. Aspects must not depend on the existence of a view for their operation.
104 */
105 QWidget* Spreadsheet::view() const {
106  if (!m_partView) {
107  bool readOnly = (this->parentAspect()->type() == AspectType::DatapickerCurve);
108  m_view = new SpreadsheetView(const_cast<Spreadsheet*>(this), readOnly);
109  m_partView = m_view;
110  }
111  return m_partView;
112 }
113 
115  return m_view->exportView();
116 }
117 
119  return m_view->printView();
120 }
121 
123  return m_view->printPreview();
124 }
125 
128 }
129 
131  for (auto* col : children<Column>())
132  col->updateLocale();
133 }
134 
135 /*!
136  Returns the maximum number of rows in the spreadsheet.
137  */
139  int result = 0;
140  for (auto* col : children<Column>()) {
141  const int col_rows = col->rowCount();
142  if ( col_rows > result)
143  result = col_rows;
144  }
145  return result;
146 }
147 
148 void Spreadsheet::removeRows(int first, int count) {
149  if ( count < 1 || first < 0 || first+count > rowCount()) return;
150  WAIT_CURSOR;
151  beginMacro( i18np("%1: remove 1 row", "%1: remove %2 rows", name(), count) );
152  for (auto* col : children<Column>())
153  col->removeRows(first, count);
154  endMacro();
155  RESET_CURSOR;
156 }
157 
158 void Spreadsheet::insertRows(int before, int count) {
159  if ( count < 1 || before < 0 || before > rowCount()) return;
160  WAIT_CURSOR;
161  beginMacro( i18np("%1: insert 1 row", "%1: insert %2 rows", name(), count) );
162  for (auto* col : children<Column>())
163  col->insertRows(before, count);
164  endMacro();
165  RESET_CURSOR;
166 }
167 
168 void Spreadsheet::appendRows(int count) {
169  insertRows(rowCount(), count);
170 }
171 
173  insertRows(rowCount(), 1);
174 }
175 
176 void Spreadsheet::appendColumns(int count) {
177  insertColumns(columnCount(), count);
178 }
179 
182 }
183 
185  insertColumns(0, count);
186 }
187 
188 /*!
189  Sets the number of rows of the spreadsheet to \c new_size
190 */
191 void Spreadsheet::setRowCount(int new_size) {
192  int current_size = rowCount();
193  if (new_size > current_size)
194  insertRows(current_size, new_size-current_size);
195  if (new_size < current_size && new_size >= 0)
196  removeRows(new_size, current_size-new_size);
197 }
198 
199 /*!
200  Returns the column with the number \c index.
201  Shallow wrapper around \sa AbstractAspect::child() - see there for caveat.
202 */
203 Column* Spreadsheet::column(int index) const {
204  return child<Column>(index);
205 }
206 
207 /*!
208  Returns the column with the name \c name.
209 */
210 Column* Spreadsheet::column(const QString &name) const {
211  return child<Column>(name);
212 }
213 
214 /*!
215  Returns the total number of columns in the spreadsheet.
216 */
218  return childCount<Column>();
219 }
220 
221 /*!
222  Returns the number of columns matching the given designation.
223  */
225  int count = 0;
226  for (auto* col : children<Column>())
227  if (col->plotDesignation() == pd)
228  count++;
229  return count;
230 }
231 
232 void Spreadsheet::removeColumns(int first, int count) {
233  if ( count < 1 || first < 0 || first+count > columnCount()) return;
234  WAIT_CURSOR;
235  beginMacro( i18np("%1: remove 1 column", "%1: remove %2 columns", name(), count) );
236  for (int i = 0; i < count; i++)
237  child<Column>(first)->remove();
238  endMacro();
239  RESET_CURSOR;
240 }
241 
242 void Spreadsheet::insertColumns(int before, int count) {
243  WAIT_CURSOR;
244  beginMacro( i18np("%1: insert 1 column", "%1: insert %2 columns", name(), count) );
245  Column * before_col = column(before);
246  int rows = rowCount();
247  for (int i = 0; i < count; i++) {
248  Column * new_col = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric);
250  new_col->insertRows(0, rows);
251  insertChildBefore(new_col, before_col);
252  }
253  endMacro();
254  RESET_CURSOR;
255 }
256 /*!
257  Sets the number of columns to \c new_size
258 */
259 void Spreadsheet::setColumnCount(int new_size) {
260  int old_size = columnCount();
261  if ( old_size == new_size || new_size < 0 )
262  return;
263 
264  if (new_size < old_size)
265  removeColumns(new_size, old_size-new_size);
266  else
267  insertColumns(old_size, new_size-old_size);
268 }
269 
270 /*!
271  Clears the whole spreadsheet.
272 */
274  WAIT_CURSOR;
275  beginMacro(i18n("%1: clear", name()));
276  for (auto* col : children<Column>())
277  col->clear();
278  endMacro();
279  RESET_CURSOR;
280 }
281 
282 /*!
283  Clears all mask in the spreadsheet.
284 */
286  WAIT_CURSOR;
287  beginMacro(i18n("%1: clear all masks", name()));
288  for (auto* col : children<Column>())
289  col->clearMasks();
290  endMacro();
291  RESET_CURSOR;
292 }
293 
294 /*!
295  Returns a new context menu. The caller takes ownership of the menu.
296 */
298  QMenu* menu = AbstractPart::createContextMenu();
299  Q_ASSERT(menu);
300  emit requestProjectContextMenu(menu);
301  return menu;
302 }
303 
304 void Spreadsheet::moveColumn(int from, int to) {
305  Column* col = child<Column>(from);
306  beginMacro(i18n("%1: move column %2 from position %3 to %4.", name(), col->name(), from+1, to+1));
307  col->remove();
308  insertChildBefore(col, child<Column>(to));
309  endMacro();
310 }
311 
313  WAIT_CURSOR;
314  beginMacro(i18n("%1: copy %2", name(), other->name()));
315 
316  for (auto* col : children<Column>())
317  col->remove();
318  for (auto* src_col : other->children<Column>()) {
319  Column * new_col = new Column(src_col->name(), src_col->columnMode());
320  new_col->copy(src_col);
321  new_col->setPlotDesignation(src_col->plotDesignation());
322  QVector< Interval<int> > masks = src_col->maskedIntervals();
323  for (const auto& iv : masks)
324  new_col->setMasked(iv);
325  QVector< Interval<int> > formulas = src_col->formulaIntervals();
326  for (const auto& iv : formulas)
327  new_col->setFormula(iv, src_col->formula(iv.start()));
328  new_col->setWidth(src_col->width());
329  addChild(new_col);
330  }
331  setComment(other->comment());
332 
333  endMacro();
334  RESET_CURSOR;
335 }
336 
337 // FIXME: replace index-based API with Column*-based one
338 /*!
339  Determines the corresponding X column.
340 */
341 int Spreadsheet::colX(int col) {
342  for (int i = col-1; i >= 0; i--) {
343  if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::X)
344  return i;
345  }
346  int cols = columnCount();
347  for (int i = col+1; i < cols; i++) {
348  if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::X)
349  return i;
350  }
351  return -1;
352 }
353 
354 /*!
355  Determines the corresponding Y column.
356 */
357 int Spreadsheet::colY(int col) {
358  int cols = columnCount();
359 
360  if (column(col)->plotDesignation() == AbstractColumn::PlotDesignation::XError ||
361  column(col)->plotDesignation() == AbstractColumn::PlotDesignation::YError) {
362  // look to the left first
363  for (int i = col-1; i >= 0; i--) {
364  if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
365  return i;
366  }
367  for (int i = col+1; i < cols; i++) {
368  if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
369  return i;
370  }
371  } else {
372  // look to the right first
373  for (int i = col+1; i < cols; i++) {
374  if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
375  return i;
376  }
377  for (int i = col-1; i >= 0; i--) {
378  if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y)
379  return i;
380  }
381  }
382  return -1;
383 }
384 
385 /*! Sorts the given list of column.
386  If 'leading' is a null pointer, each column is sorted separately.
387 */
388 void Spreadsheet::sortColumns(Column* leading, const QVector<Column*>& cols, bool ascending) {
389  DEBUG("Spreadsheet::sortColumns() : ascending = " << ascending)
390  if (cols.isEmpty()) return;
391 
392 
393  // the normal QPair comparison does not work properly with descending sorting
394  // therefore we use our own compare functions
395  // TODO: check this. a < b vs. a.first < b.first
396  class CompareFunctions {
397  public:
398  static bool doubleLess(QPair<double, int> a, QPair<double, int> b) {
399  return a.first < b.first;
400  }
401  static bool doubleGreater(QPair<double, int> a, QPair<double, int> b) {
402  return a.first > b.first;
403  }
404  static bool integerLess(QPair<int, int> a, QPair<int, int> b) {
405  return a.first < b.first;
406  }
407  static bool integerGreater(QPair<int, int> a, QPair<int, int> b) {
408  return a.first > b.first;
409  }
410  static bool bigIntLess(QPair<qint64, int> a, QPair<qint64, int> b) {
411  return a.first < b.first;
412  }
413  static bool bigIntGreater(QPair<qint64, int> a, QPair<qint64, int> b) {
414  return a.first > b.first;
415  }
416  static bool QStringLess(const QPair<QString, int>& a, const QPair<QString, int>& b) {
417  return a < b;
418  }
419  static bool QStringGreater(const QPair<QString, int>& a, const QPair<QString, int>& b) {
420  return a > b;
421  }
422  static bool QDateTimeLess(const QPair<QDateTime, int>& a, const QPair<QDateTime, int>& b) {
423  return a < b;
424  }
425  static bool QDateTimeGreater(const QPair<QDateTime, int>& a, const QPair<QDateTime, int>& b) {
426  return a > b;
427  }
428  };
429 
430  WAIT_CURSOR;
431  beginMacro(i18n("%1: sort columns", name()));
432 
433  if (leading == nullptr) { // sort separately
434  DEBUG(" sort separately")
435  for (auto* col : cols) {
436  int rows = col->rowCount();
437  std::unique_ptr<Column> tempCol(new Column("temp", col->columnMode()));
438 
439  switch (col->columnMode()) {
442 
443  for (int i = 0; i < rows; i++)
444  if (col->isValid(i))
445  map.append(QPair<double, int>(col->valueAt(i), i));
446  const int filledRows = map.size();
447 
448  if (ascending)
449  std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess);
450  else
451  std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater);
452 
453  // put the values in the right order into tempCol
454  for (int i = 0; i < filledRows; i++) {
455  int idx = map.at(i).second;
456  //too slow: tempCol->copy(col, idx, i, 1);
457  tempCol->setFromColumn(i, col, idx);
458  tempCol->setMasked(col->isMasked(idx));
459  }
460  break;
461  }
464 
465  for (int i = 0; i < rows; i++)
466  map.append(QPair<int, int>(col->valueAt(i), i));
467 
468  if (ascending)
469  std::stable_sort(map.begin(), map.end(), CompareFunctions::integerLess);
470  else
471  std::stable_sort(map.begin(), map.end(), CompareFunctions::integerGreater);
472 
473  // put the values in the right order into tempCol
474  for (int i = 0; i < rows; i++) {
475  int idx = map.at(i).second;
476  //too slow: tempCol->copy(col, idx, i, 1);
477  tempCol->setFromColumn(i, col, idx);
478  tempCol->setMasked(col->isMasked(idx));
479  }
480  break;
481  }
484 
485  for (int i = 0; i < rows; i++)
486  map.append(QPair<qint64, int>(col->valueAt(i), i));
487 
488  if (ascending)
489  std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntLess);
490  else
491  std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntGreater);
492 
493  // put the values in the right order into tempCol
494  for (int i = 0; i < rows; i++) {
495  int idx = map.at(i).second;
496  //too slow: tempCol->copy(col, idx, i, 1);
497  tempCol->setFromColumn(i, col, idx);
498  tempCol->setMasked(col->isMasked(idx));
499  }
500  break;
501  }
504 
505  for (int i = 0; i < rows; i++)
506  if (!col->textAt(i).isEmpty())
507  map.append(QPair<QString, int>(col->textAt(i), i));
508  const int filledRows = map.size();
509 
510  if (ascending)
511  std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringLess);
512  else
513  std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringGreater);
514 
515  // put the values in the right order into tempCol
516  for (int i = 0; i < filledRows; i++) {
517  int idx = map.at(i).second;
518  //too slow: tempCol->copy(col, idx, i, 1);
519  tempCol->setFromColumn(i, col, idx);
520  tempCol->setMasked(col->isMasked(idx));
521  }
522  break;
523  }
528 
529  for (int i = 0; i < rows; i++)
530  if (col->isValid(i))
531  map.append(QPair<QDateTime, int>(col->dateTimeAt(i), i));
532  const int filledRows = map.size();
533 
534  if (ascending)
535  std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeLess);
536  else
537  std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeGreater);
538 
539  // put the values in the right order into tempCol
540  for (int i = 0; i < filledRows; i++) {
541  int idx = map.at(i).second;
542  //too slow: tempCol->copy(col, idx, i, 1);
543  tempCol->setFromColumn(i, col, idx);
544  tempCol->setMasked(col->isMasked(idx));
545  }
546  break;
547  }
548  }
549  // copy the sorted column
550  col->copy(tempCol.get(), 0, 0, rows);
551  }
552  } else { // sort with leading column
553  DEBUG(" sort with leading column")
554  int rows = leading->rowCount();
555 
556  switch (leading->columnMode()) {
559  QVector<int> invalidIndex;
560 
561  for (int i = 0; i < rows; i++)
562  if (leading->isValid(i))
563  map.append(QPair<double, int>(leading->valueAt(i), i));
564  else
565  invalidIndex << i;
566  const int filledRows = map.size();
567  const int invalidRows = invalidIndex.size();
568 
569  if (ascending)
570  std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess);
571  else
572  std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater);
573 
574  for (auto* col : cols) {
575  auto columnMode = col->columnMode();
576  std::unique_ptr<Column> tempCol(new Column("temp", columnMode));
577  // put the values in correct order into tempCol
578  for (int i = 0; i < filledRows; i++) {
579  int idx = map.at(i).second;
580  //too slow: tempCol->copy(col, idx, i, 1);
581  tempCol->setFromColumn(i, col, idx);
582  tempCol->setMasked(col->isMasked(idx));
583  }
584 
585  // copy the sorted column
586  if (col == leading) // update all rows
587  col->copy(tempCol.get(), 0, 0, rows);
588  else { // do not overwrite unused cols
589  std::unique_ptr<Column> tempInvalidCol(new Column("temp2", col->columnMode()));
590  for (int i = 0; i < invalidRows; i++) {
591  const int idx = invalidIndex.at(i);
592  //too slow: tempInvalidCol->copy(col, idx, i, 1);
593  tempInvalidCol->setFromColumn(i, col, idx);
594  tempInvalidCol->setMasked(col->isMasked(idx));
595  }
596  col->copy(tempCol.get(), 0, 0, filledRows);
597  col->copy(tempInvalidCol.get(), 0, filledRows, invalidRows);
598  }
599  }
600  break;
601  }
603  //TODO: check if still working when invalid integer entries are supported
605 
606  for (int i = 0; i < rows; i++)
607  map.append(QPair<int, int>(leading->valueAt(i), i));
608 
609  if (ascending)
610  std::stable_sort(map.begin(), map.end(), CompareFunctions::integerLess);
611  else
612  std::stable_sort(map.begin(), map.end(), CompareFunctions::integerGreater);
613 
614  for (auto* col : cols) {
615  std::unique_ptr<Column> tempCol(new Column("temp", col->columnMode()));
616  // put the values in the right order into tempCol
617  for (int i = 0; i < rows; i++) {
618  int idx = map.at(i).second;
619  //too slow: tempCol->copy(col, idx, i, 1);
620  tempCol->setFromColumn(i, col, idx);
621  tempCol->setMasked(col->isMasked(idx));
622  }
623  // copy the sorted column
624  col->copy(tempCol.get(), 0, 0, rows);
625  }
626  break;
627  }
630 
631  for (int i = 0; i < rows; i++)
632  map.append(QPair<qint64, int>(leading->valueAt(i), i));
633 
634  if (ascending)
635  std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntLess);
636  else
637  std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntGreater);
638 
639  for (auto* col : cols) {
640  std::unique_ptr<Column> tempCol(new Column("temp", col->columnMode()));
641  // put the values in the right order into tempCol
642  for (int i = 0; i < rows; i++) {
643  int idx = map.at(i).second;
644  //too slow: tempCol->copy(col, idx, i, 1);
645  tempCol->setFromColumn(i, col, idx);
646  tempCol->setMasked(col->isMasked(idx));
647  }
648  // copy the sorted column
649  col->copy(tempCol.get(), 0, 0, rows);
650  }
651  break;
652  }
655  QVector<int> emptyIndex;
656 
657  for (int i = 0; i < rows; i++)
658  if (!leading->textAt(i).isEmpty())
659  map.append(QPair<QString, int>(leading->textAt(i), i));
660  else
661  emptyIndex << i;
662  //QDEBUG(" empty indices: " << emptyIndex)
663  const int filledRows = map.size();
664  const int emptyRows = emptyIndex.size();
665 
666  if (ascending)
667  std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringLess);
668  else
669  std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringGreater);
670 
671  for (auto* col : cols) {
672  std::unique_ptr<Column> tempCol(new Column("temp", col->columnMode()));
673  // put the values in the right order into tempCol
674  for (int i = 0; i < filledRows; i++) {
675  int idx = map.at(i).second;
676  //too slow: tempCol->copy(col, idx, i, 1);
677  tempCol->setFromColumn(i, col, idx);
678  tempCol->setMasked(col->isMasked(idx));
679  }
680 
681  // copy the sorted column
682  if (col == leading) // update all rows
683  col->copy(tempCol.get(), 0, 0, rows);
684  else { // do not overwrite unused cols
685  std::unique_ptr<Column> tempEmptyCol(new Column("temp2", col->columnMode()));
686  for (int i = 0; i < emptyRows; i++) {
687  const int idx = emptyIndex.at(i);
688  //too slow: tempEmptyCol->copy(col, idx, i, 1);
689  tempEmptyCol->setFromColumn(i, col, idx);
690  tempEmptyCol->setMasked(col->isMasked(idx));
691  }
692  col->copy(tempCol.get(), 0, 0, filledRows);
693  col->copy(tempEmptyCol.get(), 0, filledRows, emptyRows);
694  }
695  }
696  break;
697  }
702  QVector<int> invalidIndex;
703 
704  for (int i = 0; i < rows; i++)
705  if (leading->isValid(i))
706  map.append(QPair<QDateTime, int>(leading->dateTimeAt(i), i));
707  else
708  invalidIndex << i;
709  const int filledRows = map.size();
710  const int invalidRows = invalidIndex.size();
711 
712  if (ascending)
713  std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeLess);
714  else
715  std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeGreater);
716 
717  for (auto* col : cols) {
718  std::unique_ptr<Column> tempCol(new Column("temp", col->columnMode()));
719  // put the values in the right order into tempCol
720  for (int i = 0; i < filledRows; i++) {
721  int idx = map.at(i).second;
722  //too slow: tempCol->copy(col, idx, i, 1);
723  tempCol->setFromColumn(i, col, idx);
724  tempCol->setMasked(col->isMasked(idx));
725  }
726  // copy the sorted column
727  if (col == leading) // update all rows
728  col->copy(tempCol.get(), 0, 0, rows);
729  else { // do not overwrite unused cols
730  std::unique_ptr<Column> tempInvalidCol(new Column("temp2", col->columnMode()));
731  for (int i = 0; i < invalidRows; i++) {
732  const int idx = invalidIndex.at(i);
733  //too slow: tempInvalidCol->copy(col, idx, i, 1);
734  tempInvalidCol->setFromColumn(i, col, idx);
735  tempInvalidCol->setMasked(col->isMasked(idx));
736  }
737  col->copy(tempCol.get(), 0, 0, filledRows);
738  col->copy(tempInvalidCol.get(), 0, filledRows, invalidRows);
739  }
740  }
741  break;
742  }
743  }
744  }
745 
746  endMacro();
747  RESET_CURSOR;
748 } // end of sortColumns()
749 
750 /*!
751  Returns an icon to be used for decorating my views.
752  */
753 QIcon Spreadsheet::icon() const {
754  return QIcon::fromTheme("labplot-spreadsheet");
755 }
756 
757 /*!
758  Returns the text displayed in the given cell.
759 */
760 QString Spreadsheet::text(int row, int col) const {
761  Column* c = column(col);
762  if (!c)
763  return QString();
764 
765  return c->asStringColumn()->textAt(row);
766 }
767 
768 /*!
769  * This slot is, indirectly, called when a child of \c Spreadsheet (i.e. column) was selected in \c ProjectExplorer.
770  * Emits the signal \c columnSelected that is handled in \c SpreadsheetView.
771  */
773  const Column* column = qobject_cast<const Column*>(aspect);
774  if (column) {
775  int index = indexOfChild<Column>(column);
776  emit columnSelected(index);
777  }
778 }
779 
780 /*!
781  * This slot is, indirectly, called when a child of \c Spreadsheet (i.e. column) was deselected in \c ProjectExplorer.
782  * Emits the signal \c columnDeselected that is handled in \c SpreadsheetView.
783  */
785  const Column* column = qobject_cast<const Column*>(aspect);
786  if (column) {
787  int index = indexOfChild<Column>(column);
788  emit columnDeselected(index);
789  }
790 }
791 
792 /*!
793  * Emits the signal to select or to deselect the column number \c index in the project explorer,
794  * if \c selected=true or \c selected=false, respectively.
795  * The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer.
796  * This function is called in \c SpreadsheetView upon selection changes.
797  */
798 void Spreadsheet::setColumnSelectedInView(int index, bool selected) {
799  if (selected) {
800  emit childAspectSelectedInView(child<Column>(index));
801 
802  //deselect the spreadsheet in the project explorer, if a child (column) was selected
803  //and also all possible parents like folder, workbook, datapicker curve, datapicker
804  //to prevents unwanted multiple selection in the project explorer
805  //if one of the parents of the selected column was also selected before.
806  AbstractAspect* parent = this;
807  while (parent) {
810  }
811  } else
812  emit childAspectDeselectedInView(child<Column>(index));
813 }
814 
815 //##############################################################################
816 //################## Serialization/Deserialization ###########################
817 //##############################################################################
818 /*!
819  Saves as XML.
820  */
821 void Spreadsheet::save(QXmlStreamWriter* writer) const {
822  writer->writeStartElement("spreadsheet");
823  writeBasicAttributes(writer);
824  writeCommentElement(writer);
825 
826  //columns
827  for (auto* col : children<Column>(ChildIndexFlag::IncludeHidden))
828  col->save(writer);
829 
830  writer->writeEndElement(); // "spreadsheet"
831 }
832 
833 /*!
834  Loads from XML.
835 */
836 bool Spreadsheet::load(XmlStreamReader* reader, bool preview) {
837  if (!readBasicAttributes(reader))
838  return false;
839 
840  // read child elements
841  while (!reader->atEnd()) {
842  reader->readNext();
843 
844  if (reader->isEndElement()) break;
845 
846  if (reader->isStartElement()) {
847  if (reader->name() == "comment") {
848  if (!readCommentElement(reader)) return false;
849  } else if (reader->name() == "column") {
850  Column* column = new Column(QString());
851  if (!column->load(reader, preview)) {
852  delete column;
853  setColumnCount(0);
854  return false;
855  }
857  } else { // unknown element
858  reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
859  if (!reader->skipToEndElement()) return false;
860  }
861  }
862  }
863 
864  return !reader->hasError();
865 }
866 
867 //##############################################################################
868 //######################## Data Import #######################################
869 //##############################################################################
870 int Spreadsheet::prepareImport(std::vector<void*>& dataContainer, AbstractFileFilter::ImportMode importMode,
871  int actualRows, int actualCols, QStringList colNameList, QVector<AbstractColumn::ColumnMode> columnMode) {
872  DEBUG("Spreadsheet::prepareImport()")
873  DEBUG(" resize spreadsheet to rows = " << actualRows << " and cols = " << actualCols)
874  QDEBUG(" column name list = " << colNameList)
875  int columnOffset = 0;
876  setUndoAware(false);
877  if (m_model != nullptr)
878  m_model->suppressSignals(true);
879 
880  //make the available columns undo unaware before we resize and rename them below,
881  //the same will be done for new columns in this->resize().
882  for (int i = 0; i < childCount<Column>(); i++)
883  child<Column>(i)->setUndoAware(false);
884 
885  columnOffset = this->resize(importMode, colNameList, actualCols);
886 
887  // resize the spreadsheet
888  if (importMode == AbstractFileFilter::ImportMode::Replace) {
889  clear();
890  setRowCount(actualRows);
891  } else {
892  if (rowCount() < actualRows)
893  setRowCount(actualRows);
894  }
895 
896  if (columnMode.size() < actualCols) {
897  qWarning("columnMode[] size is too small! Giving up.");
898  return -1;
899  }
900 
901  dataContainer.resize(actualCols);
902  for (int n = 0; n < actualCols; n++) {
903  // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp)
904  Column* column = this->child<Column>(columnOffset+n);
905  DEBUG(" column " << n << " columnMode = " << static_cast<int>(columnMode[n]));
906  column->setColumnModeFast(columnMode[n]);
907 
908  //in the most cases the first imported column is meant to be used as x-data.
909  //Other columns provide mostly y-data or errors.
910  //TODO: this has to be configurable for the user in the import widget,
911  //it should be possible to specify x-error plot designation, etc.
913  column->setPlotDesignation(desig);
914 
915  switch (columnMode[n]) {
917  auto* vector = static_cast<QVector<double>*>(column->data());
918  vector->resize(actualRows);
919  dataContainer[n] = static_cast<void*>(vector);
920  break;
921  }
923  auto* vector = static_cast<QVector<int>*>(column->data());
924  vector->resize(actualRows);
925  dataContainer[n] = static_cast<void*>(vector);
926  break;
927  }
929  auto* vector = static_cast<QVector<qint64>*>(column->data());
930  vector->resize(actualRows);
931  dataContainer[n] = static_cast<void*>(vector);
932  break;
933  }
935  auto* vector = static_cast<QVector<QString>*>(column->data());
936  vector->resize(actualRows);
937  dataContainer[n] = static_cast<void*>(vector);
938  break;
939  }
943  auto* vector = static_cast<QVector<QDateTime>* >(column->data());
944  vector->resize(actualRows);
945  dataContainer[n] = static_cast<void*>(vector);
946  break;
947  }
948  }
949  }
950 // QDEBUG("dataPointers =" << dataPointers);
951 
952  DEBUG("Spreadsheet::prepareImport() DONE");
953 
954  return columnOffset;
955 }
956 
957 /*!
958  resize data source to cols columns
959  returns column offset depending on import mode
960 */
961 int Spreadsheet::resize(AbstractFileFilter::ImportMode mode, QStringList colNameList, int cols) {
962  DEBUG("Spreadsheet::resize()")
963  QDEBUG(" column name list = " << colNameList)
964  // name additional columns
965  for (int k = colNameList.size(); k < cols; k++ )
966  colNameList.append( "Column " + QString::number(k+1) );
967 
968  int columnOffset = 0; //indexes the "start column" in the spreadsheet. Starting from this column the data will be imported.
969 
970  Column* newColumn = nullptr;
972  columnOffset = childCount<Column>();
973  for (int n = 0; n < cols; n++ ) {
974  newColumn = new Column(colNameList.at(n), AbstractColumn::ColumnMode::Numeric);
975  newColumn->setUndoAware(false);
976  addChild(newColumn);
977  }
979  Column* firstColumn = child<Column>(0);
980  for (int n = 0; n < cols; n++ ) {
981  newColumn = new Column(colNameList.at(n), AbstractColumn::ColumnMode::Numeric);
982  newColumn->setUndoAware(false);
983  insertChildBefore(newColumn, firstColumn);
984  }
986  //replace completely the previous content of the data source with the content to be imported.
987  int columns = childCount<Column>();
988 
989 
990  if (columns > cols) {
991  //there're more columns in the data source then required -> remove the superfluous columns
992  for (int i = 0; i < columns-cols; i++)
993  removeChild(child<Column>(0));
994  } else {
995  //create additional columns if needed
996  for (int i = columns; i < cols; i++) {
997  newColumn = new Column(colNameList.at(i), AbstractColumn::ColumnMode::Numeric);
998  newColumn->setUndoAware(false);
999  addChildFast(newColumn); //in the replace mode, we can skip checking the uniqueness of the names and use the "fast" method
1000  }
1001  }
1002 
1003  // 1. rename the columns that were already available
1004  // 2. suppress the dataChanged signal for all columns
1005  // 3. send aspectDescriptionChanged because otherwise the column
1006  // will not be connected again to the curves (project.cpp, descriptionChanged)
1007  for (int i = 0; i < childCount<Column>(); i++) {
1008  child<Column>(i)->setSuppressDataChangedSignal(true);
1009  emit child<Column>(i)->reset(child<Column>(i));
1010  child<Column>(i)->setName(colNameList.at(i));
1011  child<Column>(i)->aspectDescriptionChanged(child<Column>(i));
1012  }
1013  }
1014 
1015  return columnOffset;
1016 }
1017 
1018 void Spreadsheet::finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode importMode) {
1019  DEBUG("Spreadsheet::finalizeImport()");
1020 
1021  //determine the dependent plots
1023  if (importMode == AbstractFileFilter::ImportMode::Replace) {
1024  for (int n = startColumn; n <= endColumn; n++) {
1025  Column* column = this->column(columnOffset + n - startColumn);
1026  column->addUsedInPlots(plots);
1027  }
1028 
1029  //suppress retransform in the dependent plots
1030  for (auto* plot : plots)
1031  plot->setSuppressDataChangedSignal(true);
1032  }
1033 
1034  // set the comments for each of the columns if datasource is a spreadsheet
1035  const int rows = rowCount();
1036  for (int n = startColumn; n <= endColumn; n++) {
1037  Column* column = this->column(columnOffset + n - startColumn);
1038  DEBUG(" column " << n << " of type " << static_cast<int>(column->columnMode()));
1039 
1040  QString comment;
1041  switch (column->columnMode()) {
1043  comment = i18np("numerical data, %1 element", "numerical data, %1 elements", rows);
1044  break;
1046  comment = i18np("integer data, %1 element", "integer data, %1 elements", rows);
1047  break;
1049  comment = i18np("big integer data, %1 element", "big integer data, %1 elements", rows);
1050  break;
1052  comment = i18np("text data, %1 element", "text data, %1 elements", rows);
1053  break;
1055  comment = i18np("month data, %1 element", "month data, %1 elements", rows);
1056  break;
1058  comment = i18np("day data, %1 element", "day data, %1 elements", rows);
1059  break;
1061  comment = i18np("date and time data, %1 element", "date and time data, %1 elements", rows);
1062  // set same datetime format in column
1063  auto* filter = static_cast<DateTime2StringFilter*>(column->outputFilter());
1064  filter->setFormat(dateTimeFormat);
1065  }
1067 
1068  if (importMode == AbstractFileFilter::ImportMode::Replace) {
1070  column->setChanged();
1071  }
1072  }
1073 
1074  if (importMode == AbstractFileFilter::ImportMode::Replace) {
1075  //retransform the dependent plots
1076  for (auto* plot : plots) {
1077  plot->setSuppressDataChangedSignal(false);
1078  plot->dataChanged();
1079  }
1080  }
1081 
1082  //make the spreadsheet and all its children undo aware again
1083  setUndoAware(true);
1084  for (int i = 0; i < childCount<Column>(); i++)
1085  child<Column>(i)->setUndoAware(true);
1086 
1087  if (m_model != nullptr)
1088  m_model->suppressSignals(false);
1089 
1090  if (m_partView != nullptr && m_view != nullptr)
1091  m_view->resizeHeader();
1092 
1093  //row count most probably changed after the import, notify the dock widget.
1094  //no need to notify about the column cound change, this is done already because of add/removeChild signals
1096 
1097  DEBUG("Spreadsheet::finalizeImport() DONE");
1098 }
AspectType
Base class of all persistent objects in a Project.
AspectType type() const
void setUndoAware(bool)
bool readCommentElement(XmlStreamReader *)
Load comment from an XML element.
@ IncludeHidden
Include aspects marked as "hidden" in numbering or listing children.
void addChildFast(AbstractAspect *)
Add the given Aspect to my list of children without any checks and without putting this step onto the...
void addChild(AbstractAspect *)
Add the given Aspect to my list of children.
void childAspectSelectedInView(const AbstractAspect *)
AbstractAspect * parent(AspectType type) const
In the parent-child hierarchy, return the first parent of type.
void childAspectDeselectedInView(const AbstractAspect *)
void remove()
Remove me from my parent's list of children.
QString name() const
QString comment() const
void writeBasicAttributes(QXmlStreamWriter *) const
Save name and creation time to XML.
void selected(const AbstractAspect *)
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)
QVector< AbstractAspect * > children(AspectType type, ChildIndexFlags flags={}) const
bool readBasicAttributes(XmlStreamReader *)
Load name and creation time from XML.
void writeCommentElement(QXmlStreamWriter *) const
Save the comment to XML.
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 setComment(const QString &)
void endMacro()
End the current undo stack macro.
void insertRows(int before, int count)
Insert some empty (or initialized with invalid values) rows.
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.
Interface for the data sources.
QMenu * createContextMenu() override
Return AbstractAspect::createContextMenu() plus operations on the primary view.
QWidget * m_partView
Definition: AbstractPart.h:65
QString textAt(int) const override
Return the content of row 'row'.
Aspect that manages a column.
Definition: Column.h:42
ColumnStringIO * asStringColumn() const
Return a wrapper column object used for String I/O.
Definition: Column.cpp:1451
QDateTime dateTimeAt(int) const override
Return the QDateTime in row 'row'.
Definition: Column.cpp:968
int rowCount() const override
Return the data vector size.
Definition: Column.cpp:1395
bool copy(const AbstractColumn *) override
Copy another column of the same type.
Definition: Column.cpp:348
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
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 setFormula(const QString &formula, const QStringList &variableNames, const QVector< Column * > &columns, bool autoUpdate)
Sets the formula used to generate column values.
Definition: Column.cpp:471
void setColumnModeFast(AbstractColumn::ColumnMode)
Definition: Column.cpp:313
double valueAt(int) const override
Return the double value in row 'row'.
Definition: Column.cpp:975
void * data() const
Definition: Column.cpp:852
void addUsedInPlots(QVector< CartesianPlot * > &)
Definition: Column.cpp:250
bool load(XmlStreamReader *, bool preview) override
Load the column from XML.
Definition: Column.cpp:1152
Conversion filter QDateTime -> QString.
void setFormat(const QString &format)
Set the format string to be used for conversion.
Model for the access to a Spreadsheet.
void suppressSignals(bool)
View class for Spreadsheet.
Aspect providing a spreadsheet table with column logic.
Definition: Spreadsheet.h:40
QString text(int row, int col) const
void requestProjectContextMenu(QMenu *)
void finalizeImport(int columnOffset, int startColumn, int endColumn, const QString &dateTimeFormat, AbstractFileFilter::ImportMode) override
int columnCount() const
void updateHorizontalHeader()
void save(QXmlStreamWriter *) const override
int resize(AbstractFileFilter::ImportMode, QStringList colNameList, int cols)
int rowCount() const
void childSelected(const AbstractAspect *) override
bool exportView() const override
void columnDeselected(int)
Spreadsheet(const QString &name, bool loading=false, AspectType type=AspectType::Spreadsheet)
Definition: Spreadsheet.cpp:68
int prepareImport(std::vector< void * > &dataContainer, AbstractFileFilter::ImportMode, int rows, int cols, QStringList colNameList, QVector< AbstractColumn::ColumnMode >) override
void updateLocale()
SpreadsheetView * m_view
Definition: Spreadsheet.h:114
Column * column(int index) const
void appendRows(int)
void removeColumns(int first, int count)
void insertColumns(int before, int count)
void clearMasks()
bool printPreview() const override
void copy(Spreadsheet *other)
SpreadsheetModel * m_model
Definition: Spreadsheet.h:111
void removeRows(int first, int count)
void insertRows(int before, int count)
void moveColumn(int from, int to)
void setColumnCount(int)
void sortColumns(Column *leading, const QVector< Column * > &, bool ascending)
void childDeselected(const AbstractAspect *) override
void appendColumns(int)
void setRowCount(int)
void prependColumns(int)
int colY(int col)
QMenu * createContextMenu() override
int colX(int col)
void appendRow()
void appendColumn()
void setColumnSelectedInView(int index, bool selected)
QWidget * view() const override
QIcon icon() const override
SpreadsheetModel * model()
Definition: Spreadsheet.cpp:97
void setModel(SpreadsheetModel *)
Definition: Spreadsheet.cpp:93
bool load(XmlStreamReader *, bool preview) override
bool printView() override
void columnSelected(int)
void rowCountChanged(int)
XML stream parser that supports errors as well as warnings. This class also adds line and column numb...
void raiseWarning(const QString &)
#define WAIT_CURSOR
Definition: macros.h:63
#define RESET_CURSOR
Definition: macros.h:64
#define DEBUG(x)
Definition: macros.h:50
#define QDEBUG(x)
Definition: macros.h:47
#define i18n(m)
Definition: nsl_common.h:38