File indexing completed on 2024-05-12 03:48:26

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