"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /***************************************************************************
    2     File                 : 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"
   32 #include "backend/core/AspectPrivate.h"
   33 #include "backend/core/AbstractAspect.h"
   34 #include "backend/core/column/ColumnStringIO.h"
   35 #include "backend/core/datatypes/DateTime2StringFilter.h"
   36 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
   37 #include "commonfrontend/spreadsheet/SpreadsheetView.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 */
   78 void Spreadsheet::init() {
   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);
   87         new_col->setPlotDesignation(i == 0 ? AbstractColumn::PlotDesignation::X : AbstractColumn::PlotDesignation::Y);
   88         addChild(new_col);
   89     }
   90     setRowCount(rows);
   91 }
   92 
   93 void Spreadsheet::setModel(SpreadsheetModel* model) {
   94     m_model = model;
   95 }
   96 
   97 SpreadsheetModel* Spreadsheet::model() {
   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 
  114 bool Spreadsheet::exportView() const {
  115     return m_view->exportView();
  116 }
  117 
  118 bool Spreadsheet::printView() {
  119     return m_view->printView();
  120 }
  121 
  122 bool Spreadsheet::printPreview() const {
  123     return m_view->printPreview();
  124 }
  125 
  126 void Spreadsheet::updateHorizontalHeader() {
  127     m_model->updateHorizontalHeader();
  128 }
  129 
  130 void Spreadsheet::updateLocale() {
  131     for (auto* col : children<Column>())
  132         col->updateLocale();
  133 }
  134 
  135 /*!
  136   Returns the maximum number of rows in the spreadsheet.
  137  */
  138 int Spreadsheet::rowCount() const {
  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 
  172 void Spreadsheet::appendRow() {
  173     insertRows(rowCount(), 1);
  174 }
  175 
  176 void Spreadsheet::appendColumns(int count) {
  177     insertColumns(columnCount(), count);
  178 }
  179 
  180 void Spreadsheet::appendColumn() {
  181     insertColumns(columnCount(), 1);
  182 }
  183 
  184 void Spreadsheet::prependColumns(int count) {
  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 */
  217 int Spreadsheet::columnCount() const {
  218     return childCount<Column>();
  219 }
  220 
  221 /*!
  222   Returns the number of columns matching the given designation.
  223  */
  224 int Spreadsheet::columnCount(AbstractColumn::PlotDesignation pd) const {
  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);
  249         new_col->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
  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 */
  273 void Spreadsheet::clear() {
  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 */
  285 void Spreadsheet::clearMasks() {
  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 */
  297 QMenu* Spreadsheet::createContextMenu() {
  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 
  312 void Spreadsheet::copy(Spreadsheet* other) {
  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()) {
  440             case AbstractColumn::ColumnMode::Numeric: {
  441                     QVector< QPair<double, int> > map;
  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                 }
  462             case AbstractColumn::ColumnMode::Integer: {
  463                     QVector< QPair<int, int> > map;
  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                 }
  482             case AbstractColumn::ColumnMode::BigInt: {
  483                     QVector< QPair<qint64, int> > map;
  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                 }
  502             case AbstractColumn::ColumnMode::Text: {
  503                     QVector<QPair<QString, int>> map;
  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                 }
  524             case AbstractColumn::ColumnMode::DateTime:
  525             case AbstractColumn::ColumnMode::Month:
  526             case AbstractColumn::ColumnMode::Day: {
  527                     QVector< QPair<QDateTime, int> > map;
  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()) {
  557         case AbstractColumn::ColumnMode::Numeric: {
  558                 QVector<QPair<double, int>> map;
  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             }
  602         case AbstractColumn::ColumnMode::Integer: {
  603                 //TODO: check if still working when invalid integer entries are supported
  604                 QVector<QPair<int, int>> map;
  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             }
  628         case AbstractColumn::ColumnMode::BigInt: {
  629                 QVector<QPair<qint64, int>> map;
  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             }
  653         case AbstractColumn::ColumnMode::Text: {
  654                 QVector<QPair<QString, int>> map;
  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             }
  698         case AbstractColumn::ColumnMode::DateTime:
  699         case AbstractColumn::ColumnMode::Month:
  700         case AbstractColumn::ColumnMode::Day: {
  701                 QVector<QPair<QDateTime, int>> map;
  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  */
  772 void Spreadsheet::childSelected(const AbstractAspect* aspect) {
  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  */
  784 void Spreadsheet::childDeselected(const AbstractAspect* aspect) {
  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) {
  808             emit childAspectDeselectedInView(parent);
  809             parent = parent->parentAspect();
  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                 }
  856                 addChildFast(column);
  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.
  912         auto desig =  (n == 0) ? AbstractColumn::PlotDesignation::X : AbstractColumn::PlotDesignation::Y;
  913         column->setPlotDesignation(desig);
  914 
  915         switch (columnMode[n]) {
  916         case AbstractColumn::ColumnMode::Numeric: {
  917             auto* vector = static_cast<QVector<double>*>(column->data());
  918             vector->resize(actualRows);
  919             dataContainer[n] = static_cast<void*>(vector);
  920             break;
  921         }
  922         case AbstractColumn::ColumnMode::Integer: {
  923             auto* vector = static_cast<QVector<int>*>(column->data());
  924             vector->resize(actualRows);
  925             dataContainer[n] = static_cast<void*>(vector);
  926             break;
  927         }
  928         case AbstractColumn::ColumnMode::BigInt: {
  929             auto* vector = static_cast<QVector<qint64>*>(column->data());
  930             vector->resize(actualRows);
  931             dataContainer[n] = static_cast<void*>(vector);
  932             break;
  933         }
  934         case AbstractColumn::ColumnMode::Text: {
  935             auto* vector = static_cast<QVector<QString>*>(column->data());
  936             vector->resize(actualRows);
  937             dataContainer[n] = static_cast<void*>(vector);
  938             break;
  939         }
  940         case AbstractColumn::ColumnMode::Month:
  941         case AbstractColumn::ColumnMode::Day:
  942         case AbstractColumn::ColumnMode::DateTime: {
  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;
  971     if (mode == AbstractFileFilter::ImportMode::Append) {
  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         }
  978     } else if (mode == AbstractFileFilter::ImportMode::Prepend) {
  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         }
  985     } else if (mode == AbstractFileFilter::ImportMode::Replace) {
  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
 1022     QVector<CartesianPlot*> 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()) {
 1042         case AbstractColumn::ColumnMode::Numeric:
 1043             comment = i18np("numerical data, %1 element", "numerical data, %1 elements", rows);
 1044             break;
 1045         case AbstractColumn::ColumnMode::Integer:
 1046             comment = i18np("integer data, %1 element", "integer data, %1 elements", rows);
 1047             break;
 1048         case AbstractColumn::ColumnMode::BigInt:
 1049             comment = i18np("big integer data, %1 element", "big integer data, %1 elements", rows);
 1050             break;
 1051         case AbstractColumn::ColumnMode::Text:
 1052             comment = i18np("text data, %1 element", "text data, %1 elements", rows);
 1053             break;
 1054         case AbstractColumn::ColumnMode::Month:
 1055             comment = i18np("month data, %1 element", "month data, %1 elements", rows);
 1056             break;
 1057         case AbstractColumn::ColumnMode::Day:
 1058             comment = i18np("day data, %1 element", "day data, %1 elements", rows);
 1059             break;
 1060         case AbstractColumn::ColumnMode::DateTime:
 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         }
 1066         column->setComment(comment);
 1067 
 1068         if (importMode == AbstractFileFilter::ImportMode::Replace) {
 1069             column->setSuppressDataChangedSignal(false);
 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
 1095     rowCountChanged(rowCount());
 1096 
 1097     DEBUG("Spreadsheet::finalizeImport() DONE");
 1098 }