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

0001 /***************************************************************************
0002     File                 : MatrixView.cpp
0003     Project              : LabPlot
0004     Description          : View class for Matrix
0005     --------------------------------------------------------------------
0006     Copyright            : (C) 2008-2009 Tilman Benkert (thzs@gmx.net)
0007     Copyright            : (C) 2015-2019 Alexander Semke (alexander.semke@web.de)
0008     Copyright            : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn)
0009 
0010  ***************************************************************************/
0011 
0012 /***************************************************************************
0013  *                                                                         *
0014  *  This program is free software; you can redistribute it and/or modify   *
0015  *  it under the terms of the GNU General Public License as published by   *
0016  *  the Free Software Foundation; either version 2 of the License, or      *
0017  *  (at your option) any later version.                                    *
0018  *                                                                         *
0019  *  This program is distributed in the hope that it will be useful,        *
0020  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0021  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0022  *  GNU General Public License for more details.                           *
0023  *                                                                         *
0024  *   You should have received a copy of the GNU General Public License     *
0025  *   along with this program; if not, write to the Free Software           *
0026  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0027  *   Boston, MA  02110-1301  USA                                           *
0028  *                                                                         *
0029  ***************************************************************************/
0030 
0031 
0032 #include "commonfrontend/matrix/MatrixView.h"
0033 #include "backend/matrix/Matrix.h"
0034 #include "backend/matrix/MatrixModel.h"
0035 #include "backend/matrix/matrixcommands.h"
0036 #include "backend/lib/macros.h"
0037 #include "backend/core/column/Column.h"
0038 #include "backend/core/column/ColumnPrivate.h"
0039 
0040 #include "kdefrontend/spreadsheet/AddSubtractValueDialog.h"
0041 #include "kdefrontend/matrix/MatrixFunctionDialog.h"
0042 #include "kdefrontend/spreadsheet/StatisticsDialog.h"
0043 
0044 #include <KLocalizedString>
0045 
0046 #include <QAction>
0047 #include <QStackedWidget>
0048 #include <QTableView>
0049 #include <QKeyEvent>
0050 #include <QMenu>
0051 #include <QPainter>
0052 #include <QPrinter>
0053 #include <QScrollArea>
0054 #include <QInputDialog>
0055 #include <QClipboard>
0056 #include <QMimeData>
0057 #include <QTextStream>
0058 #include <QThreadPool>
0059 #include <QMutex>
0060 #include <QProcess>
0061 #include <QHeaderView>
0062 #include <QIcon>
0063 
0064 #include <cfloat>
0065 #include <cmath>
0066 
0067 MatrixView::MatrixView(Matrix* matrix) : QWidget(),
0068     m_stackedWidget(new QStackedWidget(this)),
0069     m_tableView(new QTableView(this)),
0070     m_imageLabel(new QLabel(this)),
0071     m_matrix(matrix),
0072     m_model(new MatrixModel(matrix)) {
0073 
0074     init();
0075 
0076     //resize the view to show a 10x10 region of the matrix.
0077     //no need to resize the view when the project is being opened,
0078     //all views will be resized to the stored values at the end
0079     if (!m_matrix->isLoading()) {
0080         int w = m_tableView->horizontalHeader()->sectionSize(0)*10 + m_tableView->verticalHeader()->width();
0081         int h = m_tableView->verticalHeader()->sectionSize(0)*10 + m_tableView->horizontalHeader()->height();
0082         resize(w+50, h+50);
0083     }
0084 }
0085 
0086 MatrixView::~MatrixView() {
0087     delete m_model;
0088 }
0089 
0090 MatrixModel* MatrixView::model() const {
0091     return m_model;
0092 }
0093 
0094 void MatrixView::init() {
0095     initActions();
0096     connectActions();
0097     initMenus();
0098 
0099     auto* layout = new QHBoxLayout(this);
0100     layout->setContentsMargins(0, 0, 0, 0);
0101     setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding));
0102     setFocusPolicy(Qt::StrongFocus);
0103     setFocus();
0104     installEventFilter(this);
0105 
0106     layout->addWidget(m_stackedWidget);
0107 
0108     //table data view
0109     m_tableView->setModel(m_model);
0110     m_stackedWidget->addWidget(m_tableView);
0111 
0112     //horizontal header
0113     QHeaderView* h_header = m_tableView->horizontalHeader();
0114     h_header->setSectionsMovable(false);
0115     h_header->installEventFilter(this);
0116 
0117     //vertical header
0118     QHeaderView* v_header = m_tableView->verticalHeader();
0119     v_header->setSectionsMovable(false);
0120     v_header->installEventFilter(this);
0121 
0122     //set the header sizes to the (potentially user customized) sizes stored in Matrix
0123     adjustHeaders();
0124 
0125     //image view
0126     auto* area = new QScrollArea(this);
0127     m_stackedWidget->addWidget(area);
0128     area->setWidget(m_imageLabel);
0129 
0130     //SLOTs
0131     connect(m_matrix, &Matrix::requestProjectContextMenu, this, &MatrixView::createContextMenu);
0132     connect(m_model, &MatrixModel::changed, this, &MatrixView::matrixDataChanged);
0133 
0134     //keyboard shortcuts
0135     sel_all = new QShortcut(QKeySequence(tr("Ctrl+A", "Matrix: select all")), m_tableView);
0136     connect(sel_all, &QShortcut::activated, m_tableView, &QTableView::selectAll);
0137 
0138     //TODO: add shortcuts for copy&paste,
0139     //for a single shortcut we need to descriminate between copy&paste for columns, rows or selected cells.
0140 }
0141 
0142 void MatrixView::initActions() {
0143     // selection related actions
0144     action_cut_selection = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cu&t"), this);
0145     action_copy_selection = new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy"), this);
0146     action_paste_into_selection = new QAction(QIcon::fromTheme("edit-paste"), i18n("Past&e"), this);
0147     action_clear_selection = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selection"), this);
0148     action_select_all = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this);
0149 
0150     // matrix related actions
0151     auto* viewActionGroup = new QActionGroup(this);
0152     viewActionGroup->setExclusive(true);
0153     action_data_view = new QAction(QIcon::fromTheme("labplot-matrix"), i18n("Data"), viewActionGroup);
0154     action_data_view->setCheckable(true);
0155     action_data_view->setChecked(true);
0156     action_image_view = new QAction(QIcon::fromTheme("image-x-generic"), i18n("Image"), viewActionGroup);
0157     action_image_view->setCheckable(true);
0158     connect(viewActionGroup, &QActionGroup::triggered, this, &MatrixView::switchView);
0159 
0160     action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this);
0161     action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this);
0162     action_clear_matrix = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Matrix"), this);
0163     action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this);
0164 
0165     action_transpose = new QAction(i18n("&Transpose"), this);
0166     action_mirror_horizontally = new QAction(QIcon::fromTheme("object-flip-horizontal"), i18n("Mirror &Horizontally"), this);
0167     action_mirror_vertically = new QAction(QIcon::fromTheme("object-flip-vertical"), i18n("Mirror &Vertically"), this);
0168 
0169     action_add_value = new QAction(i18n("Add Value"), this);
0170     action_add_value->setData(AddSubtractValueDialog::Add);
0171     action_subtract_value = new QAction(i18n("Subtract Value"), this);
0172     action_subtract_value->setData(AddSubtractValueDialog::Subtract);
0173     action_multiply_value = new QAction(i18n("Multiply Value"), this);
0174     action_multiply_value->setData(AddSubtractValueDialog::Multiply);
0175     action_divide_value = new QAction(i18n("Divide Value"), this);
0176     action_divide_value->setData(AddSubtractValueDialog::Divide);
0177 
0178 //  action_duplicate = new QAction(i18nc("duplicate matrix", "&Duplicate"), this);
0179     //TODO
0180     //icon
0181     auto* headerFormatActionGroup = new QActionGroup(this);
0182     headerFormatActionGroup->setExclusive(true);
0183     action_header_format_1= new QAction(i18n("Rows and Columns"), headerFormatActionGroup);
0184     action_header_format_1->setCheckable(true);
0185     action_header_format_2= new QAction(i18n("xy-Values"), headerFormatActionGroup);
0186     action_header_format_2->setCheckable(true);
0187     action_header_format_3= new QAction(i18n("Rows, Columns and xy-Values"), headerFormatActionGroup);
0188     action_header_format_3->setCheckable(true);
0189     connect(headerFormatActionGroup, &QActionGroup::triggered, this, &MatrixView::headerFormatChanged);
0190 
0191     // column related actions
0192     action_add_columns = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("&Add Columns"), this);
0193     action_insert_columns = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("&Insert Empty Columns"), this);
0194     action_remove_columns = new QAction(QIcon::fromTheme("edit-table-delete-column"), i18n("Remo&ve Columns"), this);
0195     action_clear_columns = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Columns"), this);
0196     action_statistics_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this);
0197 
0198     // row related actions
0199     action_add_rows = new QAction(QIcon::fromTheme("edit-table-insert-row-above"), i18n("&Add Rows"), this);
0200     action_insert_rows = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("&Insert Empty Rows"), this);
0201     action_remove_rows = new QAction(QIcon::fromTheme("edit-table-delete-row"), i18n("Remo&ve Rows"), this);
0202     action_clear_rows = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Rows"), this);
0203     action_statistics_rows = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this);
0204 }
0205 
0206 void MatrixView::modifyValues() {
0207     const QAction* action = dynamic_cast<const QAction*>(QObject::sender());
0208     AddSubtractValueDialog::Operation op = (AddSubtractValueDialog::Operation)action->data().toInt();
0209     auto* dlg = new AddSubtractValueDialog(m_matrix, op);
0210     dlg->setMatrices();
0211     dlg->exec();
0212 }
0213 
0214 void MatrixView::connectActions() {
0215     // selection related actions
0216     connect(action_cut_selection, &QAction::triggered, this, &MatrixView::cutSelection);
0217     connect(action_copy_selection, &QAction::triggered, this, &MatrixView::copySelection);
0218     connect(action_paste_into_selection, &QAction::triggered, this, &MatrixView::pasteIntoSelection);
0219     connect(action_clear_selection, &QAction::triggered, this, &MatrixView::clearSelectedCells);
0220     connect(action_select_all, &QAction::triggered, m_tableView, &QTableView::selectAll);
0221 
0222     // matrix related actions
0223     connect(action_fill_function, &QAction::triggered, this, &MatrixView::fillWithFunctionValues);
0224     connect(action_fill_const, &QAction::triggered, this, &MatrixView::fillWithConstValues);
0225 
0226     connect(action_go_to_cell, &QAction::triggered, this, QOverload<>::of(&MatrixView::goToCell));
0227     //connect(action_duplicate, &QAction::triggered, this, &MatrixView::duplicate);
0228     connect(action_clear_matrix, &QAction::triggered, m_matrix, &Matrix::clear);
0229     connect(action_transpose, &QAction::triggered, m_matrix, &Matrix::transpose);
0230     connect(action_mirror_horizontally, &QAction::triggered, m_matrix, &Matrix::mirrorHorizontally);
0231     connect(action_mirror_vertically, &QAction::triggered, m_matrix, &Matrix::mirrorVertically);
0232     connect(action_add_value, &QAction::triggered, this, &MatrixView::modifyValues);
0233     connect(action_subtract_value, &QAction::triggered, this, &MatrixView::modifyValues);
0234     connect(action_multiply_value, &QAction::triggered, this, &MatrixView::modifyValues);
0235     connect(action_divide_value, &QAction::triggered, this, &MatrixView::modifyValues);
0236 
0237     // column related actions
0238     connect(action_add_columns, &QAction::triggered, this, &MatrixView::addColumns);
0239     connect(action_insert_columns, &QAction::triggered, this, &MatrixView::insertEmptyColumns);
0240     connect(action_remove_columns, &QAction::triggered, this, &MatrixView::removeSelectedColumns);
0241     connect(action_clear_columns, &QAction::triggered, this, &MatrixView::clearSelectedColumns);
0242     connect(action_statistics_columns, &QAction::triggered, this, &MatrixView::showColumnStatistics);
0243 
0244     // row related actions
0245     connect(action_add_rows, &QAction::triggered, this, &MatrixView::addRows);
0246     connect(action_insert_rows, &QAction::triggered, this, &MatrixView::insertEmptyRows);
0247     connect(action_remove_rows, &QAction::triggered, this, &MatrixView::removeSelectedRows);
0248     connect(action_clear_rows, &QAction::triggered, this, &MatrixView::clearSelectedRows);
0249     connect(action_statistics_rows, &QAction::triggered, this, &MatrixView::showRowStatistics);
0250 }
0251 
0252 void MatrixView::initMenus() {
0253     //selection menu
0254     m_selectionMenu = new QMenu(i18n("Selection"), this);
0255     m_selectionMenu->setIcon(QIcon::fromTheme("selection"));
0256     m_selectionMenu->addAction(action_cut_selection);
0257     m_selectionMenu->addAction(action_copy_selection);
0258     m_selectionMenu->addAction(action_paste_into_selection);
0259     m_selectionMenu->addAction(action_clear_selection);
0260 
0261     //column menu
0262     m_columnMenu = new QMenu(this);
0263     m_columnMenu->addAction(action_insert_columns);
0264     m_columnMenu->addAction(action_remove_columns);
0265     m_columnMenu->addAction(action_clear_columns);
0266     m_columnMenu->addAction(action_statistics_columns);
0267 
0268     //row menu
0269     m_rowMenu = new QMenu(this);
0270     m_rowMenu->addAction(action_insert_rows);
0271     m_rowMenu->addAction(action_remove_rows);
0272     m_rowMenu->addAction(action_clear_rows);
0273     m_rowMenu->addAction(action_statistics_rows);
0274 
0275     //matrix menu
0276     m_matrixMenu = new QMenu(this);
0277 
0278     m_matrixMenu->addMenu(m_selectionMenu);
0279     m_matrixMenu->addSeparator();
0280 
0281     QMenu* submenu = new QMenu(i18n("Generate Data"), this);
0282     submenu->addAction(action_fill_const);
0283     submenu->addAction(action_fill_function);
0284     m_matrixMenu->addMenu(submenu);
0285     m_matrixMenu->addSeparator();
0286 
0287     // Data manipulation sub-menu
0288     QMenu* dataManipulationMenu = new QMenu(i18n("Manipulate Data"), this);
0289     dataManipulationMenu->addAction(action_add_value);
0290     dataManipulationMenu->addAction(action_subtract_value);
0291     dataManipulationMenu->addAction(action_multiply_value);
0292     dataManipulationMenu->addAction(action_divide_value);
0293     dataManipulationMenu->addSeparator();
0294     dataManipulationMenu->addAction(action_mirror_horizontally);
0295     dataManipulationMenu->addAction(action_mirror_vertically);
0296     dataManipulationMenu->addSeparator();
0297     dataManipulationMenu->addAction(action_transpose);
0298 
0299     m_matrixMenu->addMenu(dataManipulationMenu);
0300     m_matrixMenu->addSeparator();
0301 
0302     submenu = new QMenu(i18n("View"), this);
0303     submenu->setIcon(QIcon::fromTheme("view-choose"));
0304     submenu->addAction(action_data_view);
0305     submenu->addAction(action_image_view);
0306     m_matrixMenu->addMenu(submenu);
0307     m_matrixMenu->addSeparator();
0308 
0309     m_matrixMenu->addAction(action_select_all);
0310     m_matrixMenu->addAction(action_clear_matrix);
0311     m_matrixMenu->addSeparator();
0312 
0313     m_headerFormatMenu = new QMenu(i18n("Header Format"), this);
0314     m_headerFormatMenu->setIcon(QIcon::fromTheme("format-border-style"));
0315     m_headerFormatMenu->addAction(action_header_format_1);
0316     m_headerFormatMenu->addAction(action_header_format_2);
0317     m_headerFormatMenu->addAction(action_header_format_3);
0318 
0319     m_matrixMenu->addMenu(m_headerFormatMenu);
0320     m_matrixMenu->addSeparator();
0321     m_matrixMenu->addAction(action_go_to_cell);
0322 }
0323 
0324 /*!
0325  * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions.
0326  * The menu is used
0327  *   - as the context menu in MatrixView
0328  *   - as the "matrix menu" in the main menu-bar (called form MainWin)
0329  *   - as a part of the matrix context menu in project explorer
0330  */
0331 void MatrixView::createContextMenu(QMenu* menu) const {
0332     Q_ASSERT(menu);
0333 
0334     QAction* firstAction = nullptr;
0335     // if we're populating the context menu for the project explorer, then
0336     //there're already actions available there. Skip the first title-action
0337     //and insert the action at the beginning of the menu.
0338     if (menu->actions().size()>1)
0339         firstAction = menu->actions().at(1);
0340 
0341     menu->insertMenu(firstAction, m_selectionMenu);
0342     menu->insertSeparator(firstAction);
0343 
0344     QMenu* submenu = new QMenu(i18n("Generate Data"), const_cast<MatrixView*>(this));
0345     submenu->addAction(action_fill_const);
0346     submenu->addAction(action_fill_function);
0347     menu->insertMenu(firstAction, submenu);
0348     menu->insertSeparator(firstAction);
0349 
0350     // Data manipulation sub-menu
0351     submenu = new QMenu(i18n("Manipulate Data"), const_cast<MatrixView*>(this));
0352     submenu->addAction(action_transpose);
0353     submenu->addAction(action_mirror_horizontally);
0354     submenu->addAction(action_mirror_vertically);
0355     submenu->addAction(action_add_value);
0356     submenu->addAction(action_subtract_value);
0357     submenu->addAction(action_multiply_value);
0358     submenu->addAction(action_divide_value);
0359 
0360     menu->insertMenu(firstAction, submenu);
0361     menu->insertSeparator(firstAction);
0362 
0363     submenu = new QMenu(i18n("View"), const_cast<MatrixView*>(this));
0364     submenu->addAction(action_data_view);
0365     submenu->addAction(action_image_view);
0366     menu->insertMenu(firstAction, submenu);
0367     menu->insertSeparator(firstAction);
0368 
0369     menu->insertAction(firstAction, action_select_all);
0370     menu->insertAction(firstAction, action_clear_matrix);
0371     menu->insertSeparator(firstAction);
0372 //  menu->insertAction(firstAction, action_duplicate);
0373     menu->insertMenu(firstAction, m_headerFormatMenu);
0374 
0375     menu->insertSeparator(firstAction);
0376     menu->insertAction(firstAction, action_go_to_cell);
0377     menu->insertSeparator(firstAction);
0378 }
0379 
0380 /*!
0381     set the row and column size to the saved sizes.
0382  */
0383 void MatrixView::adjustHeaders() {
0384     QHeaderView* h_header = m_tableView->horizontalHeader();
0385     QHeaderView* v_header = m_tableView->verticalHeader();
0386 
0387     disconnect(v_header, &QHeaderView::sectionResized, this, &MatrixView::handleVerticalSectionResized);
0388     disconnect(h_header, &QHeaderView::sectionResized, this, &MatrixView::handleHorizontalSectionResized);
0389 
0390     //resize columns to the saved sizes or to fit the contents if the widht is 0
0391     int cols = m_matrix->columnCount();
0392     for (int i = 0; i < cols; i++) {
0393         if (m_matrix->columnWidth(i) == 0)
0394             m_tableView->resizeColumnToContents(i);
0395         else
0396             m_tableView->setColumnWidth(i, m_matrix->columnWidth(i));
0397     }
0398 
0399     //resize rows to the saved sizes or to fit the contents if the height is 0
0400     int rows = m_matrix->rowCount();
0401     for (int i = 0; i < rows; i++) {
0402         if (m_matrix->rowHeight(i) == 0)
0403             m_tableView->resizeRowToContents(i);
0404         else
0405             m_tableView->setRowHeight(i, m_matrix->rowHeight(i));
0406     }
0407 
0408     connect(v_header, &QHeaderView::sectionResized, this, &MatrixView::handleVerticalSectionResized);
0409     connect(h_header, &QHeaderView::sectionResized, this, &MatrixView::handleHorizontalSectionResized);
0410 }
0411 
0412 /*!
0413     Resizes the headers/columns to fit the new content. Called on changes of the header format in Matrix.
0414 */
0415 void MatrixView::resizeHeaders() {
0416     m_tableView->resizeColumnsToContents();
0417     m_tableView->resizeRowsToContents();
0418 
0419     if (m_matrix->headerFormat() == Matrix::HeaderFormat::HeaderRowsColumns)
0420         action_header_format_1->setChecked(true);
0421     else if (m_matrix->headerFormat() == Matrix::HeaderFormat::HeaderValues)
0422         action_header_format_2->setChecked(true);
0423     else
0424         action_header_format_3->setChecked(true);
0425 }
0426 
0427 /*!
0428     Returns how many columns are selected.
0429     If full is true, this function only returns the number of fully selected columns.
0430 */
0431 int MatrixView::selectedColumnCount(bool full) const {
0432     int count = 0;
0433     int cols = m_matrix->columnCount();
0434     for (int i = 0; i < cols; i++)
0435         if (isColumnSelected(i, full)) count++;
0436     return count;
0437 }
0438 
0439 /*!
0440     Returns true if column 'col' is selected; otherwise false.
0441     If full is true, this function only returns true if the whole column is selected.
0442 */
0443 bool MatrixView::isColumnSelected(int col, bool full) const {
0444     if (full)
0445         return m_tableView->selectionModel()->isColumnSelected(col, QModelIndex());
0446     else
0447         return m_tableView->selectionModel()->columnIntersectsSelection(col, QModelIndex());
0448 }
0449 
0450 /*!
0451     Return how many rows are (at least partly) selected
0452     If full is true, this function only returns the number of fully selected rows.
0453 */
0454 int MatrixView::selectedRowCount(bool full) const {
0455     int count = 0;
0456     int rows = m_matrix->rowCount();
0457     for (int i = 0; i < rows; i++)
0458         if (isRowSelected(i, full)) count++;
0459     return count;
0460 }
0461 
0462 /*!
0463     Returns true if row \c row is selected; otherwise false
0464     If full is true, this function only returns true if the whole row is selected.
0465 */
0466 bool MatrixView::isRowSelected(int row, bool full) const {
0467     if (full)
0468         return m_tableView->selectionModel()->isRowSelected(row, QModelIndex());
0469     else
0470         return m_tableView->selectionModel()->rowIntersectsSelection(row, QModelIndex());
0471 }
0472 
0473 /*!
0474     Return the index of the first selected column.
0475     If full is true, this function only looks for fully selected columns.
0476 */
0477 int MatrixView::firstSelectedColumn(bool full) const {
0478     int cols = m_matrix->columnCount();
0479     for (int i = 0; i < cols; i++) {
0480         if (isColumnSelected(i, full))
0481             return i;
0482     }
0483     return -1;
0484 }
0485 
0486 /*!
0487     Return the index of the last selected column
0488     If full is true, this function only looks for fully selected columns.
0489 */
0490 int MatrixView::lastSelectedColumn(bool full) const {
0491     int cols = m_matrix->columnCount();
0492     for (int i = cols-1; i >= 0; i--)
0493         if (isColumnSelected(i, full)) return i;
0494 
0495     return -2;
0496 }
0497 
0498 /*!
0499     Return the index of the first selected row.
0500     If full is true, this function only looks for fully selected rows.
0501 */
0502 int MatrixView::firstSelectedRow(bool full) const {
0503     int rows = m_matrix->rowCount();
0504     for (int i = 0; i < rows; i++)  {
0505         if (isRowSelected(i, full))
0506             return i;
0507     }
0508     return -1;
0509 }
0510 
0511 /*!
0512     Return the index of the last selected row
0513     If full is true, this function only looks for fully selected rows.
0514 */
0515 int MatrixView::lastSelectedRow(bool full) const {
0516     int rows = m_matrix->rowCount();
0517     for (int i = rows-1; i >= 0; i--)
0518         if (isRowSelected(i, full)) return i;
0519 
0520     return -2;
0521 }
0522 
0523 bool MatrixView::isCellSelected(int row, int col) const {
0524     if (row < 0 || col < 0 || row >= m_matrix->rowCount() || col >= m_matrix->columnCount()) return false;
0525 
0526     return m_tableView->selectionModel()->isSelected(m_model->index(row, col));
0527 }
0528 
0529 void MatrixView::setCellSelected(int row, int col) {
0530     m_tableView->selectionModel()->select(m_model->index(row, col), QItemSelectionModel::Select);
0531 }
0532 
0533 void MatrixView::setCellsSelected(int first_row, int first_col, int last_row, int last_col) {
0534     QModelIndex top_left = m_model->index(first_row, first_col);
0535     QModelIndex bottom_right = m_model->index(last_row, last_col);
0536     m_tableView->selectionModel()->select(QItemSelection(top_left, bottom_right), QItemSelectionModel::SelectCurrent);
0537 }
0538 
0539 /*!
0540     Determine the current cell (-1 if no cell is designated as the current)
0541 */
0542 void MatrixView::getCurrentCell(int* row, int* col) const {
0543     QModelIndex index = m_tableView->selectionModel()->currentIndex();
0544     if (index.isValid()) {
0545         *row = index.row();
0546         *col = index.column();
0547     } else {
0548         *row = -1;
0549         *col = -1;
0550     }
0551 }
0552 
0553 bool MatrixView::eventFilter(QObject * watched, QEvent * event) {
0554     if (event->type() == QEvent::ContextMenu) {
0555         auto* cm_event = static_cast<QContextMenuEvent*>(event);
0556         QPoint global_pos = cm_event->globalPos();
0557         if (watched == m_tableView->verticalHeader())
0558             m_rowMenu->exec(global_pos);
0559         else if (watched == m_tableView->horizontalHeader())
0560             m_columnMenu->exec(global_pos);
0561         else if (watched == this)
0562             m_matrixMenu->exec(global_pos);
0563         else
0564             return QWidget::eventFilter(watched, event);
0565         return true;
0566     } else
0567         return QWidget::eventFilter(watched, event);
0568 }
0569 
0570 void MatrixView::keyPressEvent(QKeyEvent* event) {
0571     if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
0572         advanceCell();
0573     else if (event->key() == Qt::Key_Backspace || event->matches(QKeySequence::Delete))
0574         clearSelectedCells();
0575 }
0576 
0577 //##############################################################################
0578 //####################################  SLOTs   ################################
0579 //##############################################################################
0580 /*!
0581     Advance current cell after [Return] or [Enter] was pressed
0582 */
0583 void MatrixView::advanceCell() {
0584     QModelIndex idx = m_tableView->currentIndex();
0585     if (idx.row()+1 < m_matrix->rowCount())
0586         m_tableView->setCurrentIndex(idx.sibling(idx.row()+1, idx.column()));
0587 }
0588 
0589 void MatrixView::goToCell() {
0590     bool ok;
0591 
0592     int col = QInputDialog::getInt(nullptr, i18n("Go to Cell"), i18n("Enter column"), 1, 1, m_matrix->columnCount(), 1, &ok);
0593     if (!ok) return;
0594 
0595     int row = QInputDialog::getInt(nullptr, i18n("Go to Cell"), i18n("Enter row"), 1, 1, m_matrix->rowCount(), 1, &ok);
0596     if (!ok) return;
0597 
0598     goToCell(row-1, col-1);
0599 }
0600 
0601 void MatrixView::goToCell(int row, int col) {
0602     QModelIndex index = m_model->index(row, col);
0603     m_tableView->scrollTo(index);
0604     m_tableView->setCurrentIndex(index);
0605 }
0606 
0607 void MatrixView::handleHorizontalSectionResized(int logicalIndex, int oldSize, int newSize) {
0608     Q_UNUSED(oldSize)
0609     m_matrix->setColumnWidth(logicalIndex, newSize);
0610 }
0611 
0612 void MatrixView::handleVerticalSectionResized(int logicalIndex, int oldSize, int newSize) {
0613     Q_UNUSED(oldSize)
0614     m_matrix->setRowHeight(logicalIndex, newSize);
0615 }
0616 
0617 void MatrixView::fillWithFunctionValues() {
0618     auto* dlg = new MatrixFunctionDialog(m_matrix);
0619     dlg->exec();
0620 }
0621 
0622 void MatrixView::fillWithConstValues() {
0623     bool ok = false;
0624     double value = QInputDialog::getDouble(this, i18n("Fill the matrix with constant value"),
0625         i18n("Value"), 0, -2147483647, 2147483647, 6, &ok);
0626     if (ok) {
0627         WAIT_CURSOR;
0628         auto* newData = static_cast<QVector<QVector<double>>*>(m_matrix->data());
0629         for (int col = 0; col < m_matrix->columnCount(); ++col) {
0630             for (int row = 0; row < m_matrix->rowCount(); ++row)
0631                 (newData->operator[](col))[row] = value;
0632         }
0633         m_matrix->setData(newData);
0634         RESET_CURSOR;
0635     }
0636 }
0637 
0638 //############################ selection related slots #########################
0639 void MatrixView::cutSelection() {
0640     if (firstSelectedRow() < 0) return;
0641 
0642     WAIT_CURSOR;
0643     m_matrix->beginMacro(i18n("%1: cut selected cell(s)", m_matrix->name()));
0644     copySelection();
0645     clearSelectedCells();
0646     m_matrix->endMacro();
0647     RESET_CURSOR;
0648 }
0649 
0650 void MatrixView::copySelection() {
0651     int first_col = firstSelectedColumn(false);
0652     if (first_col == -1) return;
0653     int last_col = lastSelectedColumn(false);
0654     if (last_col == -2) return;
0655     int first_row = firstSelectedRow(false);
0656     if (first_row == -1)    return;
0657     int last_row = lastSelectedRow(false);
0658     if (last_row == -2) return;
0659     int cols = last_col - first_col +1;
0660     int rows = last_row - first_row +1;
0661 
0662     WAIT_CURSOR;
0663     QString output_str;
0664 
0665     SET_NUMBER_LOCALE
0666     for (int r = 0; r < rows; r++)  {
0667         for (int c = 0; c < cols; c++) {
0668             //TODO: mode
0669             if (isCellSelected(first_row + r, first_col + c))
0670                 output_str += numberLocale.toString(m_matrix->cell<double>(first_row + r, first_col + c),
0671                                                  m_matrix->numericFormat(), 16); // copy with max. precision
0672             if (c < cols-1)
0673                 output_str += '\t';
0674         }
0675         if (r < rows-1)
0676             output_str += '\n';
0677     }
0678     QApplication::clipboard()->setText(output_str);
0679     RESET_CURSOR;
0680 }
0681 
0682 void MatrixView::pasteIntoSelection() {
0683     if (m_matrix->columnCount() < 1 || m_matrix->rowCount() < 1) return;
0684 
0685     const QMimeData* mime_data = QApplication::clipboard()->mimeData();
0686     if (!mime_data->hasFormat("text/plain"))
0687         return;
0688 
0689     WAIT_CURSOR;
0690     m_matrix->beginMacro(i18n("%1: paste from clipboard", m_matrix->name()));
0691 
0692     int first_col = firstSelectedColumn(false);
0693     int last_col = lastSelectedColumn(false);
0694     int first_row = firstSelectedRow(false);
0695     int last_row = lastSelectedRow(false);
0696     int input_row_count = 0;
0697     int input_col_count = 0;
0698     int rows, cols;
0699 
0700     QString input_str = QString(mime_data->data("text/plain"));
0701     QList< QStringList > cell_texts;
0702     QStringList input_rows(input_str.split('\n'));
0703     input_row_count = input_rows.count();
0704     input_col_count = 0;
0705     for (int i = 0; i < input_row_count; i++) {
0706         cell_texts.append(input_rows.at(i).split('\t'));
0707         if (cell_texts.at(i).count() > input_col_count) input_col_count = cell_texts.at(i).count();
0708     }
0709 
0710     // if the is no selection or only one cell selected, the
0711     // selection will be expanded to the needed size from the current cell
0712     if ( (first_col == -1 || first_row == -1) ||
0713             (last_row == first_row && last_col == first_col) ) {
0714         int current_row, current_col;
0715         getCurrentCell(&current_row, &current_col);
0716         if (current_row == -1) current_row = 0;
0717         if (current_col == -1) current_col = 0;
0718         setCellSelected(current_row, current_col);
0719         first_col = current_col;
0720         first_row = current_row;
0721         last_row = first_row + input_row_count -1;
0722         last_col = first_col + input_col_count -1;
0723         // resize the matrix if necessary
0724         if (last_col >= m_matrix->columnCount())
0725             m_matrix->appendColumns(last_col+1-m_matrix->columnCount());
0726         if (last_row >= m_matrix->rowCount())
0727             m_matrix->appendRows(last_row+1-m_matrix->rowCount());
0728         // select the rectangle to be pasted in
0729         setCellsSelected(first_row, first_col, last_row, last_col);
0730     }
0731 
0732     rows = last_row - first_row + 1;
0733     cols = last_col - first_col + 1;
0734     for (int r = 0; r < rows && r < input_row_count; r++) {
0735         for (int c = 0; c < cols && c < input_col_count; c++) {
0736             if (isCellSelected(first_row + r, first_col + c) && (c < cell_texts.at(r).count()) )
0737                 m_matrix->setCell(first_row + r, first_col + c, cell_texts.at(r).at(c).toDouble());
0738         }
0739     }
0740 
0741     m_matrix->endMacro();
0742     RESET_CURSOR;
0743 }
0744 
0745 void MatrixView::clearSelectedCells() {
0746     int first_row = firstSelectedRow();
0747     if (first_row<0)
0748         return;
0749 
0750     int first_col = firstSelectedColumn();
0751     if (first_col<0)
0752         return;
0753 
0754     int last_row = lastSelectedRow();
0755     int last_col = lastSelectedColumn();
0756 
0757     WAIT_CURSOR;
0758     m_matrix->beginMacro(i18n("%1: clear selected cell(s)", m_matrix->name()));
0759     for (int i = first_row; i <= last_row; i++) {
0760         for (int j = first_col; j <= last_col; j++) {
0761             if (isCellSelected(i, j))
0762                 m_matrix->clearCell(i, j);
0763         }
0764     }
0765     m_matrix->endMacro();
0766     RESET_CURSOR;
0767 }
0768 
0769 class UpdateImageTask : public QRunnable {
0770 public:
0771     UpdateImageTask(int start, int end, QImage& image, const void* data, double scaleFactor, double min) : m_image(image), m_data(data) {
0772         m_start = start;
0773         m_end = end;
0774         m_scaleFactor = scaleFactor;
0775         m_min = min;
0776     };
0777 
0778     void run() override {
0779         for (int row = m_start; row < m_end; ++row) {
0780             m_mutex.lock();
0781             QRgb* line = reinterpret_cast<QRgb*>(m_image.scanLine(row));
0782             m_mutex.unlock();
0783             for (int col = 0; col < m_image.width(); ++col) {
0784                 const int gray = (static_cast<const QVector<QVector<double>>*>(m_data)->at(col).at(row)-m_min)*m_scaleFactor;
0785                 line[col] = qRgb(gray, gray, gray);
0786             }
0787         }
0788     }
0789 
0790 private:
0791     QMutex m_mutex;
0792     int m_start;
0793     int m_end;
0794     QImage& m_image;
0795     const void* m_data;
0796     double m_scaleFactor;
0797     double m_min;
0798 };
0799 
0800 void MatrixView::updateImage() {
0801     WAIT_CURSOR;
0802     m_image = QImage(m_matrix->columnCount(), m_matrix->rowCount(), QImage::Format_ARGB32);
0803 
0804     //find min/max value
0805     double dmax = -DBL_MAX, dmin = DBL_MAX;
0806     const QVector<QVector<double>>* data = static_cast<QVector<QVector<double>>*>(m_matrix->data());
0807     const int width = m_matrix->columnCount();
0808     const int height = m_matrix->rowCount();
0809     for (int col = 0; col < width; ++col) {
0810         for (int row = 0; row < height; ++row) {
0811             const double value = (data->operator[](col))[row];
0812             if (dmax < value) dmax = value;
0813             if (dmin > value) dmin = value;
0814         }
0815     }
0816 
0817     //update the image
0818     const double scaleFactor = 255.0/(dmax-dmin);
0819     QThreadPool* pool = QThreadPool::globalInstance();
0820     int range = ceil(double(m_image.height())/pool->maxThreadCount());
0821     for (int i = 0; i < pool->maxThreadCount(); ++i) {
0822         const int start = i*range;
0823         int end = (i+1)*range;
0824         if (end > m_image.height()) end = m_image.height();
0825         auto* task = new UpdateImageTask(start, end, m_image, data, scaleFactor, dmin);
0826         pool->start(task);
0827     }
0828     pool->waitForDone();
0829 
0830     m_imageLabel->resize(width, height);
0831     m_imageLabel->setPixmap(QPixmap::fromImage(m_image));
0832     m_imageIsDirty = false;
0833     RESET_CURSOR;
0834 }
0835 
0836 //############################# matrix related slots ###########################
0837 void MatrixView::switchView(QAction* action) {
0838     if (action == action_data_view)
0839         m_stackedWidget->setCurrentIndex(0);
0840     else {
0841         if (m_imageIsDirty)
0842             this->updateImage();
0843 
0844         m_stackedWidget->setCurrentIndex(1);
0845     }
0846 }
0847 
0848 void MatrixView::matrixDataChanged() {
0849     m_imageIsDirty = true;
0850     if (m_stackedWidget->currentIndex() == 1)
0851         this->updateImage();
0852 }
0853 
0854 void MatrixView::headerFormatChanged(QAction* action) {
0855     if (action == action_header_format_1)
0856         m_matrix->setHeaderFormat(Matrix::HeaderFormat::HeaderRowsColumns);
0857     else if (action == action_header_format_2)
0858         m_matrix->setHeaderFormat(Matrix::HeaderFormat::HeaderValues);
0859     else
0860         m_matrix->setHeaderFormat(Matrix::HeaderFormat::HeaderRowsColumnsValues);
0861 }
0862 
0863 //############################# column related slots ###########################
0864 /*!
0865   Append as many columns as are selected.
0866 */
0867 void MatrixView::addColumns() {
0868     m_matrix->appendColumns(selectedColumnCount(false));
0869 }
0870 
0871 void MatrixView::insertEmptyColumns() {
0872     int first = firstSelectedColumn();
0873     int last = lastSelectedColumn();
0874     if (first < 0) return;
0875     int current = first;
0876 
0877     WAIT_CURSOR;
0878     m_matrix->beginMacro(i18n("%1: insert empty column(s)", m_matrix->name()));
0879     while (current <= last) {
0880         current = first+1;
0881         while (current <= last && isColumnSelected(current)) current++;
0882         const int count = current-first;
0883         m_matrix->insertColumns(first, count);
0884         current += count;
0885         last += count;
0886         while (current <= last && isColumnSelected(current)) current++;
0887         first = current;
0888     }
0889     m_matrix->endMacro();
0890     RESET_CURSOR;
0891 }
0892 
0893 void MatrixView::removeSelectedColumns() {
0894     int first = firstSelectedColumn();
0895     int last = lastSelectedColumn();
0896     if (first < 0) return;
0897 
0898     WAIT_CURSOR;
0899     m_matrix->beginMacro(i18n("%1: remove selected column(s)", m_matrix->name()));
0900     for (int i = last; i >= first; i--)
0901         if (isColumnSelected(i, false)) m_matrix->removeColumns(i, 1);
0902     m_matrix->endMacro();
0903     RESET_CURSOR;
0904 }
0905 
0906 void MatrixView::clearSelectedColumns() {
0907     WAIT_CURSOR;
0908     m_matrix->beginMacro(i18n("%1: clear selected column(s)", m_matrix->name()));
0909     for (int i = 0; i < m_matrix->columnCount(); i++) {
0910         if (isColumnSelected(i, false))
0911             m_matrix->clearColumn(i);
0912     }
0913     m_matrix->endMacro();
0914     RESET_CURSOR;
0915 }
0916 
0917 //############################## rows related slots ############################
0918 /*!
0919   Append as many rows as are selected.
0920 */
0921 void MatrixView::addRows() {
0922     m_matrix->appendRows(selectedRowCount(false));
0923 }
0924 
0925 void MatrixView::insertEmptyRows() {
0926     int first = firstSelectedRow();
0927     int last = lastSelectedRow();
0928     int current = first;
0929 
0930     if (first < 0) return;
0931 
0932     WAIT_CURSOR;
0933     m_matrix->beginMacro(i18n("%1: insert empty rows(s)", m_matrix->name()));
0934     while (current <= last) {
0935         current = first+1;
0936         while (current <= last && isRowSelected(current)) current++;
0937         const int count = current-first;
0938         m_matrix->insertRows(first, count);
0939         current += count;
0940         last += count;
0941         while (current <= last && !isRowSelected(current)) current++;
0942         first = current;
0943     }
0944     m_matrix->endMacro();
0945     RESET_CURSOR;
0946 }
0947 
0948 void MatrixView::removeSelectedRows() {
0949     int first = firstSelectedRow();
0950     int last = lastSelectedRow();
0951     if (first < 0) return;
0952 
0953     WAIT_CURSOR;
0954     m_matrix->beginMacro(i18n("%1: remove selected rows(s)", m_matrix->name()));
0955     for (int i = last; i >= first; i--)
0956         if (isRowSelected(i, false)) m_matrix->removeRows(i, 1);
0957     m_matrix->endMacro();
0958     RESET_CURSOR;
0959 }
0960 
0961 void MatrixView::clearSelectedRows() {
0962     int first = firstSelectedRow();
0963     int last = lastSelectedRow();
0964     if (first < 0) return;
0965 
0966     WAIT_CURSOR;
0967     m_matrix->beginMacro(i18n("%1: clear selected rows(s)", m_matrix->name()));
0968     for (int i = first; i <= last; i++) {
0969         if (isRowSelected(i))
0970             m_matrix->clearRow(i);
0971     }
0972     m_matrix->endMacro();
0973     RESET_CURSOR;
0974 }
0975 
0976 /*!
0977   prints the complete matrix to \c printer.
0978  */
0979 
0980 void MatrixView::print(QPrinter* printer) const {
0981     WAIT_CURSOR;
0982     QPainter painter (printer);
0983 
0984     const int dpiy = printer->logicalDpiY();
0985     const int margin = (int) ( (1/2.54)*dpiy ); // 1 cm margins
0986 
0987     QHeaderView* hHeader = m_tableView->horizontalHeader();
0988     QHeaderView* vHeader = m_tableView->verticalHeader();
0989     auto* data = static_cast<QVector<QVector<double>>*>(m_matrix->data());
0990 
0991     const int rows = m_matrix->rowCount();
0992     const int cols = m_matrix->columnCount();
0993     int height = margin;
0994     const int vertHeaderWidth = vHeader->width();
0995     int right = margin + vertHeaderWidth;
0996 
0997     int columnsPerTable = 0;
0998     int headerStringWidth = 0;
0999     int firstRowStringWidth = vertHeaderWidth;
1000     bool tablesNeeded = false;
1001     QVector<int> firstRowCeilSizes;
1002     firstRowCeilSizes.reserve(data[0].size());
1003     firstRowCeilSizes.resize(data[0].size());
1004     QRect br;
1005 
1006     for (int i = 0; i < data->size(); ++i) {
1007         br = painter.boundingRect(br, Qt::AlignCenter,QString::number(data->at(i)[0]) + '\t');
1008         firstRowCeilSizes[i] = br.width() > m_tableView->columnWidth(i) ?
1009                                br.width() : m_tableView->columnWidth(i);
1010     }
1011     for (int col = 0; col < cols; ++col) {
1012         headerStringWidth += m_tableView->columnWidth(col);
1013         br = painter.boundingRect(br, Qt::AlignCenter,QString::number(data->at(col)[0]) + '\t');
1014         firstRowStringWidth += br.width();
1015         if ((headerStringWidth >= printer->pageRect().width() -2*margin) ||
1016                 (firstRowStringWidth >= printer->pageRect().width() - 2*margin)) {
1017             tablesNeeded = true;
1018             break;
1019         }
1020         columnsPerTable++;
1021     }
1022 
1023     int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0;
1024     const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols;
1025 
1026     if (!tablesNeeded) {
1027         tablesCount = 1;
1028         columnsPerTable = cols;
1029     }
1030     if (remainingColumns > 0)
1031         tablesCount++;
1032     for (int table = 0; table < tablesCount; ++table) {
1033         //Paint the horizontal header first
1034         painter.setFont(hHeader->font());
1035         QString headerString = m_tableView->model()->headerData(0, Qt::Horizontal).toString();
1036         QRect br;
1037         br = painter.boundingRect(br, Qt::AlignCenter, headerString);
1038         QRect tr(br);
1039         if (table != 0)
1040             height += tr.height();
1041         painter.drawLine(right, height, right, height+br.height());
1042 
1043         int i = table * columnsPerTable;
1044         int toI = table * columnsPerTable + columnsPerTable;
1045         if ((remainingColumns > 0) && (table == tablesCount-1)) {
1046             i = (tablesCount-1)*columnsPerTable;
1047             toI = (tablesCount-1)* columnsPerTable + remainingColumns;
1048         }
1049 
1050         for (; i<toI; ++i) {
1051             headerString = m_tableView->model()->headerData(i, Qt::Horizontal).toString();
1052             const int w = /*m_tableView->columnWidth(i)*/ firstRowCeilSizes[i];
1053             tr.setTopLeft(QPoint(right,height));
1054             tr.setWidth(w);
1055             tr.setHeight(br.height());
1056 
1057             painter.drawText(tr, Qt::AlignCenter, headerString);
1058             right += w;
1059             painter.drawLine(right, height, right, height+tr.height());
1060 
1061         }
1062         //first horizontal line
1063         painter.drawLine(margin + vertHeaderWidth, height, right-1, height);
1064         height += tr.height();
1065         painter.drawLine(margin, height, right-1, height);
1066 
1067         // print table values
1068         QString cellText;
1069         for (i = 0; i < rows; ++i) {
1070             right = margin;
1071             cellText = m_tableView->model()->headerData(i, Qt::Vertical).toString()+'\t';
1072             tr = painter.boundingRect(tr, Qt::AlignCenter, cellText);
1073             painter.drawLine(right, height, right, height+tr.height());
1074 
1075             br.setTopLeft(QPoint(right,height));
1076             br.setWidth(vertHeaderWidth);
1077             br.setHeight(tr.height());
1078             painter.drawText(br, Qt::AlignCenter, cellText);
1079             right += vertHeaderWidth;
1080             painter.drawLine(right, height, right, height+tr.height());
1081             int j = table * columnsPerTable;
1082             int toJ = table * columnsPerTable + columnsPerTable;
1083             if ((remainingColumns > 0) && (table == tablesCount-1)) {
1084                 j = (tablesCount-1)*columnsPerTable;
1085                 toJ = (tablesCount-1)* columnsPerTable + remainingColumns;
1086             }
1087             for (; j< toJ; j++) {
1088                 int w = /*m_tableView->columnWidth(j)*/ firstRowCeilSizes[j];
1089                 cellText = QString::number(data->at(j)[i]) + '\t';
1090                 tr = painter.boundingRect(tr,Qt::AlignCenter,cellText);
1091                 br.setTopLeft(QPoint(right,height));
1092                 br.setWidth(w);
1093                 br.setHeight(tr.height());
1094                 painter.drawText(br, Qt::AlignCenter, cellText);
1095                 right += w;
1096                 painter.drawLine(right, height, right, height+tr.height());
1097             }
1098             height += br.height();
1099             painter.drawLine(margin, height, right-1, height);
1100 
1101             if (height >= printer->height()-margin ) {
1102                 printer->newPage();
1103                 height = margin;
1104                 painter.drawLine(margin, height, right, height);
1105             }
1106         }
1107     }
1108     RESET_CURSOR;
1109 }
1110 
1111 void MatrixView::exportToFile(const QString& path, const QString& separator, QLocale::Language language) const {
1112     QFile file(path);
1113     if (!file.open(QFile::WriteOnly | QFile::Truncate))
1114         return;
1115 
1116     QTextStream out(&file);
1117 
1118     QString sep = separator;
1119     sep = sep.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive);
1120     sep = sep.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive);
1121 
1122     //export values
1123     const int cols = m_matrix->columnCount();
1124     const int rows = m_matrix->rowCount();
1125     const QVector<QVector<double> >* data = static_cast<QVector<QVector<double>>*>(m_matrix->data());
1126     //TODO: use general setting for number locale?
1127     QLocale locale(language);
1128     for (int row = 0; row < rows; ++row) {
1129         for (int col = 0; col < cols; ++col) {
1130             out << locale.toString(data->at(col)[row], m_matrix->numericFormat(), m_matrix->precision());
1131 
1132             out << data->at(col)[row];
1133             if (col != cols-1)
1134                 out << sep;
1135         }
1136         out << '\n';
1137     }
1138 }
1139 
1140 void MatrixView::exportToLaTeX(const QString& path, const bool verticalHeaders, const bool horizontalHeaders,
1141                                const bool latexHeaders, const bool gridLines, const bool entire, const bool captions) const {
1142     QFile file(path);
1143     if (!file.open(QFile::WriteOnly | QFile::Truncate))
1144         return;
1145 
1146     QVector<QVector<QString> > toExport;
1147 
1148     int firstSelectedCol = 0;
1149     int firstSelectedRowi = 0;
1150     int totalRowCount = 0;
1151     int cols = 0;
1152     if (entire) {
1153         cols = m_matrix->columnCount();
1154         totalRowCount = m_matrix->rowCount();
1155         toExport.reserve(totalRowCount);
1156         toExport.resize(totalRowCount);
1157         for (int row = 0; row < totalRowCount; ++row) {
1158             toExport[row].reserve(cols);
1159             toExport[row].resize(cols);
1160             //TODO: mode
1161             for (int col = 0; col < cols; ++col)
1162                 toExport[row][col] = m_matrix->text<double>(row,col);
1163         }
1164         firstSelectedCol = 0;
1165         firstSelectedRowi = 0;
1166     } else {
1167         cols = selectedColumnCount();
1168         totalRowCount = selectedRowCount();
1169 
1170         firstSelectedCol = firstSelectedColumn();
1171         if (firstSelectedCol == -1)
1172             return;
1173         firstSelectedRowi = firstSelectedRow();
1174         if (firstSelectedRowi == -1)
1175             return;
1176         const int lastSelectedCol = lastSelectedColumn();
1177         const int lastSelectedRowi = lastSelectedRow();
1178 
1179         toExport.reserve(lastSelectedRowi - firstSelectedRowi+1);
1180         toExport.resize(lastSelectedRowi - firstSelectedRowi+1);
1181         int r = 0;
1182         for (int row = firstSelectedRowi; row <= lastSelectedRowi; ++row, ++r) {
1183             toExport[r].reserve(lastSelectedCol - firstSelectedCol+1);
1184             toExport[r].resize(lastSelectedCol - firstSelectedCol+1);
1185             int c = 0;
1186             //TODO: mode
1187             for (int col = firstSelectedCol; col <= lastSelectedCol; ++col,++c)
1188                 toExport[r][c] = m_matrix->text<double>(row, col);
1189         }
1190     }
1191 
1192     int columnsStringSize = 0;
1193     int headerStringSize = 0;
1194     int columnsPerTable = 0;
1195     const int firstHHeaderSectionLength = m_tableView->model()->headerData(0, Qt::Horizontal).toString().length();
1196     const int firstSelectedVHeaderSectionLength = m_tableView->model()->headerData(firstSelectedRow(), Qt::Vertical).toString().length();
1197     if (verticalHeaders) {
1198         if (entire)
1199             headerStringSize += firstHHeaderSectionLength;
1200         else
1201             headerStringSize += firstSelectedVHeaderSectionLength;
1202     }
1203     if (!horizontalHeaders && verticalHeaders) {
1204         if (entire)
1205             columnsStringSize += firstHHeaderSectionLength;
1206         else
1207             columnsStringSize += firstSelectedVHeaderSectionLength;
1208     }
1209 
1210     for (int col = 0; col < cols; ++col) {
1211         int maxSize = -1;
1212         for (auto row : toExport) {
1213             if (row.at(col).size() > maxSize)
1214                 maxSize = row.at(col).size();
1215         }
1216         columnsStringSize += maxSize;
1217         if (horizontalHeaders)
1218             headerStringSize += m_tableView->model()->headerData(col, Qt::Horizontal).toString().length();
1219         if ((columnsStringSize > 65) || (headerStringSize > 65))
1220             break;
1221         ++columnsPerTable;
1222     }
1223 
1224     int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0;
1225     const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols;
1226 
1227     bool columnsSeparating = (cols > columnsPerTable);
1228     QTextStream out(&file);
1229 
1230     QProcess tex;
1231     tex.start("latex", QStringList() << "--version", QProcess::ReadOnly);
1232     tex.waitForFinished(500);
1233     QString texVersionOutput = QString(tex.readAllStandardOutput());
1234     texVersionOutput = texVersionOutput.split('\n')[0];
1235 
1236     int yearidx = -1;
1237     for (int i = texVersionOutput.size() - 1; i >= 0; --i) {
1238         if (texVersionOutput.at(i) == QChar('2')) {
1239             yearidx = i;
1240             break;
1241         }
1242     }
1243 
1244     if (texVersionOutput.at(yearidx+1) == QChar('/'))
1245         yearidx-=3;
1246 
1247     bool ok;
1248     texVersionOutput.midRef(yearidx, 4).toInt(&ok);
1249     int version = -1;
1250     if (ok)
1251         version = texVersionOutput.midRef(yearidx, 4).toInt(&ok);
1252 
1253     if (latexHeaders) {
1254         out << QLatin1String("\\documentclass[11pt,a4paper]{article} \n");
1255         out << QLatin1String("\\usepackage{geometry} \n");
1256         out << QLatin1String("\\usepackage{xcolor,colortbl} \n");
1257         if (version >= 2015)
1258             out << QLatin1String("\\extrafloats{1280} \n");
1259         out << QLatin1String("\\definecolor{HeaderBgColor}{rgb}{0.81,0.81,0.81} \n");
1260         out << QLatin1String("\\geometry{ \n");
1261         out << QLatin1String("a4paper, \n");
1262         out << QLatin1String("total={170mm,257mm}, \n");
1263         out << QLatin1String("left=10mm, \n");
1264         out << QLatin1String("top=10mm } \n");
1265 
1266         out << QLatin1String("\\begin{document} \n");
1267         out << QLatin1String("\\title{LabPlot Matrix Export to \\LaTeX{} } \n");
1268         out << QLatin1String("\\author{LabPlot} \n");
1269         out << QLatin1String("\\date{\\today} \n");
1270         // out << "\\maketitle \n";
1271     }
1272 
1273     const QString endTabularTable ("\\end{tabular} \n \\end{table} \n");
1274     const QString tableCaption ("\\caption{"+ m_matrix->name() + "} \n");
1275     const QString beginTable ("\\begin{table}[ht] \n");
1276     const QString centeredColumn( gridLines ? QLatin1String(" c |") : QLatin1String(" c "));
1277     int rowCount = 0;
1278     const int maxRows = 45;
1279     bool captionRemoved = false;
1280 
1281     if (columnsSeparating) {
1282         for (int table = 0; table < tablesCount; ++table) {
1283             QStringList textable;
1284             captionRemoved = false;
1285 
1286             textable << beginTable;
1287             if (captions)
1288                 textable << tableCaption;
1289             textable << QLatin1String("\\centering \n");
1290             textable << QLatin1String("\\begin{tabular}{");
1291             textable << (gridLines ? QStringLiteral("|") : QString());
1292             for (int i = 0; i < columnsPerTable; ++i)
1293                 textable << centeredColumn;
1294             if (verticalHeaders)
1295                 textable << centeredColumn;
1296             textable << QLatin1String("} \n");
1297             if (gridLines)
1298                 textable << QLatin1String("\\hline \n");
1299 
1300             if (horizontalHeaders) {
1301                 if (latexHeaders)
1302                     textable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
1303                 if (verticalHeaders)
1304                     textable << QLatin1String(" & ");
1305                 for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) {
1306                     textable << m_tableView->model()->headerData(col + firstSelectedCol, Qt::Horizontal).toString();
1307                     if (col != ((table * columnsPerTable)+ columnsPerTable)-1)
1308                         textable << QLatin1String(" & ");
1309                 }
1310                 textable << QLatin1String("\\\\ \n");
1311                 if (gridLines)
1312                     textable << QLatin1String("\\hline \n");
1313             }
1314             for (const auto& s : textable)
1315                 out << s;
1316             for (int row = 0; row < totalRowCount; ++row) {
1317                 if (verticalHeaders) {
1318                     out << "\\cellcolor{HeaderBgColor} ";
1319                     out << m_tableView->model()->headerData(row + firstSelectedRowi, Qt::Vertical).toString();
1320                     out << QLatin1String(" & ");
1321                 }
1322                 for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col ) {
1323                     out << toExport.at(row).at(col);
1324                     if (col != ((table * columnsPerTable)+ columnsPerTable)-1)
1325                         out << QLatin1String(" & ");
1326                 }
1327 
1328                 out << QLatin1String("\\\\ \n");
1329                 if (gridLines)
1330                     out << QLatin1String("\\hline \n");
1331                 rowCount++;
1332                 if (rowCount == maxRows) {
1333                     out << endTabularTable;
1334                     out << QLatin1String("\\newpage \n");
1335                     if (captions)
1336                         if (!captionRemoved)
1337                             textable.removeAt(1);
1338                     for (const auto& s : textable)
1339                         out << s;
1340                     rowCount = 0;
1341                     if (!captionRemoved)
1342                         captionRemoved = true;
1343                 }
1344             }
1345             out << endTabularTable;
1346         }
1347         captionRemoved = false;
1348 
1349         QStringList remainingTable;
1350         remainingTable << beginTable;
1351         if (captions)
1352             remainingTable << tableCaption;
1353         remainingTable << QLatin1String("\\centering \n");
1354         remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
1355         for (int c = 0; c < remainingColumns; ++c)
1356             remainingTable << centeredColumn;
1357         if (verticalHeaders)
1358             remainingTable << centeredColumn;
1359         remainingTable << QLatin1String("} \n");
1360         if (gridLines)
1361             remainingTable << QLatin1String("\\hline \n");
1362 
1363         if (horizontalHeaders) {
1364             if (latexHeaders)
1365                 remainingTable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
1366             if (verticalHeaders)
1367                 remainingTable << QLatin1String(" & ");
1368             for (int col = 0; col < remainingColumns; ++col) {
1369                 remainingTable << m_tableView->model()->headerData(firstSelectedCol+col + (tablesCount * columnsPerTable), Qt::Horizontal).toString();
1370                 if (col != remainingColumns-1)
1371                     remainingTable << QLatin1String(" & ");
1372             }
1373             remainingTable << QLatin1String("\\\\ \n");
1374             if (gridLines)
1375                 remainingTable << QLatin1String("\\hline \n");
1376         }
1377 
1378         for (const auto& s : remainingTable)
1379             out << s;
1380 
1381         for (int row = 0; row < totalRowCount; ++row) {
1382             if (verticalHeaders) {
1383                 out << "\\cellcolor{HeaderBgColor}";
1384                 out << m_tableView->model()->headerData(row+ firstSelectedRowi, Qt::Vertical).toString();
1385                 out << QLatin1String(" & ");
1386             }
1387             for (int col = 0; col < remainingColumns; ++col ) {
1388                 out << toExport.at(row).at(col + (tablesCount * columnsPerTable));
1389                 if (col != remainingColumns-1)
1390                     out << QLatin1String(" & ");
1391             }
1392 
1393             out << QLatin1String("\\\\ \n");
1394             if (gridLines)
1395                 out << QLatin1String("\\hline \n");
1396             rowCount++;
1397             if (rowCount == maxRows) {
1398                 out << endTabularTable;
1399                 out << QLatin1String("\\pagebreak[4] \n");
1400                 if (captions)
1401                     if (!captionRemoved)
1402                         remainingTable.removeAt(1);
1403                 for (const auto& s : remainingTable)
1404                     out << s;
1405                 rowCount = 0;
1406                 if (!captionRemoved)
1407                     captionRemoved = true;
1408             }
1409         }
1410         out << endTabularTable;
1411     } else {
1412         QStringList textable;
1413         textable << beginTable;
1414         if (captions)
1415             textable << tableCaption;
1416         textable << QLatin1String("\\centering \n");
1417         textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString());
1418         for (int c = 0; c < cols; ++c)
1419             textable << centeredColumn;
1420         if (verticalHeaders)
1421             textable << centeredColumn;
1422         textable << QLatin1String("} \n");
1423         if (gridLines)
1424             textable << QLatin1String("\\hline \n");
1425 
1426         if (horizontalHeaders) {
1427             if (latexHeaders)
1428                 textable << QLatin1String("\\rowcolor{HeaderBgColor} \n");
1429             if (verticalHeaders)
1430                 textable << QLatin1String(" & ");
1431             for (int col = 0; col < cols ; ++col) {
1432                 textable << m_tableView->model()->headerData(col+firstSelectedCol, Qt::Horizontal).toString();
1433                 if (col != cols-1)
1434                     textable << QLatin1String(" & ");
1435             }
1436             textable << QLatin1String("\\\\ \n");
1437             if (gridLines)
1438                 textable << QLatin1String("\\hline \n");
1439         }
1440 
1441         for (const auto& s : textable)
1442             out << s;
1443         for (int row = 0; row < totalRowCount; ++row) {
1444             if (verticalHeaders) {
1445                 out << "\\cellcolor{HeaderBgColor}";
1446                 out << m_tableView->model()->headerData(row + firstSelectedRowi, Qt::Vertical).toString();
1447                 out << QLatin1String(" & ");
1448             }
1449             for (int col = 0; col < cols; ++col ) {
1450                 out << toExport.at(row).at(col);
1451                 if (col != cols-1)
1452                     out << " & ";
1453             }
1454             out << QLatin1String("\\\\ \n");
1455             if (gridLines)
1456                 out << QLatin1String("\\hline \n");
1457             rowCount++;
1458             if (rowCount == maxRows) {
1459                 out << endTabularTable;
1460                 out << QLatin1String("\\newpage \n");
1461                 if (captions)
1462                     if (!captionRemoved)
1463                         textable.removeAt(1);
1464                 for (const auto& s : textable)
1465                     out << s;
1466                 if (!captionRemoved)
1467                     captionRemoved = true;
1468                 rowCount = 0;
1469                 if (!captionRemoved)
1470                     captionRemoved = true;
1471             }
1472         }
1473         out << endTabularTable;
1474     }
1475     if (latexHeaders)
1476         out << QLatin1String("\\end{document} \n");
1477 }
1478 
1479 void MatrixView::showColumnStatistics() {
1480     if (selectedColumnCount() > 0) {
1481         QString dlgTitle (m_matrix->name() + " column statistics");
1482         QVector<Column*> columns;
1483         for (int col = 0; col < m_matrix->columnCount(); ++col) {
1484             if (isColumnSelected(col, false)) {
1485                 QString headerString = m_tableView->model()->headerData(col, Qt::Horizontal).toString();
1486                 columns << new Column(headerString, static_cast<QVector<QVector<double>>*>(m_matrix->data())->at(col));
1487             }
1488         }
1489         auto* dlg = new StatisticsDialog(dlgTitle, columns);
1490         dlg->showStatistics();
1491         if (dlg->exec() == QDialog::Accepted) {
1492             qDeleteAll(columns);
1493             columns.clear();
1494         }
1495     }
1496 }
1497 
1498 void MatrixView::showRowStatistics() {
1499     if (selectedRowCount() > 0) {
1500         QString dlgTitle (m_matrix->name() + " row statistics");
1501         QVector<Column*> columns;
1502         for (int row = 0; row < m_matrix->rowCount(); ++row) {
1503             if (isRowSelected(row, false)) {
1504                 QString headerString = m_tableView->model()->headerData(row, Qt::Vertical).toString();
1505                 //TODO: mode
1506                 columns << new Column(headerString, m_matrix->rowCells<double>(row, 0, m_matrix->columnCount()-1));
1507             }
1508         }
1509         auto* dlg = new StatisticsDialog(dlgTitle, columns);
1510         dlg->showStatistics();
1511         if (dlg->exec() == QDialog::Accepted) {
1512             qDeleteAll(columns);
1513             columns.clear();
1514         }
1515     }
1516 }
1517 
1518 void MatrixView::exportToFits(const QString &fileName, const int exportTo) const {
1519     auto* filter = new FITSFilter;
1520     filter->setExportTo(exportTo);
1521     filter->write(fileName, m_matrix);
1522 
1523     delete filter;
1524 }