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(¤t_row, ¤t_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 }