File indexing completed on 2024-05-12 03:48:28
0001 /* 0002 File : SpreadsheetView.cpp 0003 Project : LabPlot 0004 Description : View class for Spreadsheet 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2011-2023 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2016 Fabian Kristof <fkristofszabolcs@gmail.com> 0008 SPDX-FileCopyrightText: 2020-2023 Stefan Gerlach <stefan.gerlach@uni.kn> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "SpreadsheetView.h" 0013 #include "backend/core/Project.h" 0014 #include "backend/core/Settings.h" 0015 #include "backend/core/column/Column.h" 0016 #include "backend/core/datatypes/DateTime2StringFilter.h" 0017 #include "backend/core/datatypes/Double2StringFilter.h" 0018 #include "backend/core/datatypes/SimpleCopyThroughFilter.h" 0019 #include "backend/core/datatypes/String2DateTimeFilter.h" 0020 #include "backend/core/datatypes/String2DoubleFilter.h" 0021 #include "backend/datasources/filters/FITSFilter.h" 0022 #include "backend/datasources/filters/XLSXFilter.h" 0023 #include "backend/lib/macros.h" 0024 #include "backend/lib/trace.h" 0025 #include "backend/spreadsheet/Spreadsheet.h" 0026 #include "backend/spreadsheet/SpreadsheetModel.h" 0027 #include "backend/spreadsheet/StatisticsSpreadsheet.h" 0028 #include "backend/worksheet/plots/cartesian/BoxPlot.h" //TODO: needed for the icon only, remove later once we have a breeze icon 0029 #include "backend/worksheet/plots/cartesian/CartesianPlot.h" 0030 #include "commonfrontend/spreadsheet/SpreadsheetHeaderView.h" 0031 #include "commonfrontend/spreadsheet/SpreadsheetItemDelegate.h" 0032 0033 #include "kdefrontend/spreadsheet/AddSubtractValueDialog.h" 0034 #include "kdefrontend/spreadsheet/DropValuesDialog.h" 0035 #include "kdefrontend/spreadsheet/EquidistantValuesDialog.h" 0036 #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h" 0037 #include "kdefrontend/spreadsheet/FlattenColumnsDialog.h" 0038 #include "kdefrontend/spreadsheet/FormattingHeatmapDialog.h" 0039 #include "kdefrontend/spreadsheet/FunctionValuesDialog.h" 0040 #include "kdefrontend/spreadsheet/GoToDialog.h" 0041 #include "kdefrontend/spreadsheet/PlotDataDialog.h" 0042 #include "kdefrontend/spreadsheet/RandomValuesDialog.h" 0043 #include "kdefrontend/spreadsheet/RescaleDialog.h" 0044 #include "kdefrontend/spreadsheet/SampleValuesDialog.h" 0045 #include "kdefrontend/spreadsheet/SearchReplaceWidget.h" 0046 #include "kdefrontend/spreadsheet/SortDialog.h" 0047 #include "kdefrontend/spreadsheet/StatisticsDialog.h" 0048 0049 #ifdef Q_OS_MAC 0050 #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h" 0051 #endif 0052 0053 #include <KConfigGroup> 0054 #include <KLocalizedString> 0055 #include <KMessageBox> 0056 0057 #include <QAbstractSlider> 0058 #include <QActionGroup> 0059 #include <QApplication> 0060 #include <QClipboard> 0061 #include <QDate> 0062 #include <QFile> 0063 #include <QInputDialog> 0064 #include <QKeyEvent> 0065 #include <QMenu> 0066 #include <QMessageBox> 0067 #include <QMimeData> 0068 #include <QPainter> 0069 #include <QPrintDialog> 0070 #include <QPrintPreviewDialog> 0071 #include <QPrinter> 0072 #include <QProcess> 0073 #include <QRandomGenerator> 0074 #include <QRegularExpression> 0075 #include <QScrollBar> 0076 #include <QSqlDatabase> 0077 #include <QSqlError> 0078 #include <QSqlQuery> 0079 #include <QTableView> 0080 #include <QTextStream> 0081 #include <QTimer> 0082 #include <QToolBar> 0083 #include <QUndoCommand> 0084 0085 #include <algorithm> //for std::reverse 0086 0087 #include <gsl/gsl_const_cgs.h> 0088 #include <gsl/gsl_math.h> 0089 0090 enum NormalizationMethod { 0091 DivideBySum, 0092 DivideByMin, 0093 DivideByMax, 0094 DivideByCount, 0095 DivideByMean, 0096 DivideByMedian, 0097 DivideByMode, 0098 DivideByRange, 0099 DivideBySD, 0100 DivideByMAD, 0101 DivideByIQR, 0102 ZScoreSD, 0103 ZScoreMAD, 0104 ZScoreIQR, 0105 Rescale 0106 }; 0107 0108 enum TukeyLadderPower { InverseSquared, Inverse, InverseSquareRoot, Log, SquareRoot, Squared, Cube }; 0109 0110 /*! 0111 \class SpreadsheetView 0112 \brief View class for Spreadsheet 0113 0114 \ingroup commonfrontend 0115 */ 0116 SpreadsheetView::SpreadsheetView(Spreadsheet* spreadsheet, bool readOnly) 0117 : QWidget() 0118 , m_tableView(new QTableView(this)) 0119 , m_spreadsheet(spreadsheet) 0120 , m_readOnly(readOnly) { 0121 auto* layout = new QVBoxLayout(this); 0122 layout->setContentsMargins(0, 0, 0, 0); 0123 layout->addWidget(m_tableView); 0124 if (m_readOnly) 0125 m_tableView->setEditTriggers(QTableView::NoEditTriggers); 0126 0127 init(); 0128 0129 // resize the view to show alls columns and the first 10 rows. 0130 // no need to resize the view when the project is being opened, 0131 // all views will be resized to the stored values at the end 0132 if (!m_spreadsheet->isLoading()) { 0133 int w = m_tableView->verticalHeader()->width(); 0134 int h = m_horizontalHeader->height(); 0135 for (int i = 0; i < m_horizontalHeader->count(); ++i) 0136 w += m_horizontalHeader->sectionSize(i); 0137 0138 if (m_tableView->verticalHeader()->count() <= 10) 0139 h += m_tableView->verticalHeader()->sectionSize(0) * m_tableView->verticalHeader()->count(); 0140 else 0141 h += m_tableView->verticalHeader()->sectionSize(0) * 11; 0142 0143 resize(w + 50, h); 0144 } 0145 0146 KConfigGroup group = Settings::group(QStringLiteral("Spreadsheet")); 0147 showComments(group.readEntry(QLatin1String("ShowComments"), false)); 0148 } 0149 0150 SpreadsheetView::~SpreadsheetView() { 0151 } 0152 0153 void SpreadsheetView::init() { 0154 // create a new SpreadsheetModel if not available yet. 0155 // the creation of the model is done here since it's only required 0156 // for the view but its lifecycle is managed in Spreadsheet, 0157 // i.e. the deletion of the model is done in the destructor of Spreadsheet 0158 m_model = m_spreadsheet->model(); 0159 if (!m_model) 0160 m_model = new SpreadsheetModel(m_spreadsheet); 0161 0162 m_tableView->setModel(m_model); 0163 auto* delegate = new SpreadsheetItemDelegate(this); 0164 connect(delegate, &SpreadsheetItemDelegate::returnPressed, this, &SpreadsheetView::advanceCell); 0165 connect(delegate, &SpreadsheetItemDelegate::editorEntered, this, [=]() { 0166 // action_insert_row_below->setShortcut(QKeySequence()); 0167 m_editorEntered = true; 0168 }); 0169 connect(delegate, &SpreadsheetItemDelegate::closeEditor, this, [=]() { 0170 // action_insert_row_below->setShortcut(Qt::Key_Insert); 0171 m_editorEntered = false; 0172 }); 0173 0174 m_tableView->setItemDelegate(delegate); 0175 m_tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); 0176 0177 // horizontal header 0178 m_horizontalHeader = new SpreadsheetHeaderView(this); 0179 m_horizontalHeader->setSectionsClickable(true); 0180 m_horizontalHeader->setHighlightSections(true); 0181 m_tableView->setHorizontalHeader(m_horizontalHeader); 0182 m_horizontalHeader->setSectionsMovable(true); 0183 m_horizontalHeader->installEventFilter(this); 0184 m_tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); 0185 // m_tableView->installEventFilter(this); 0186 0187 resizeHeader(); 0188 0189 connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionMoved, this, &SpreadsheetView::handleHorizontalSectionMoved); 0190 connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionDoubleClicked, this, &SpreadsheetView::handleHorizontalHeaderDoubleClicked); 0191 connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionResized, this, &SpreadsheetView::handleHorizontalSectionResized); 0192 connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionClicked, this, &SpreadsheetView::columnClicked); 0193 0194 // vertical header 0195 QHeaderView* v_header = m_tableView->verticalHeader(); 0196 v_header->setSectionResizeMode(QHeaderView::Fixed); 0197 v_header->setSectionsMovable(false); 0198 v_header->installEventFilter(this); 0199 m_tableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); 0200 0201 setFocusPolicy(Qt::StrongFocus); 0202 setFocus(); 0203 installEventFilter(this); 0204 showComments(false); 0205 0206 connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::updateHeaderGeometry); 0207 connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::handleHeaderDataChanged); 0208 connect(m_spreadsheet, &Spreadsheet::aspectsInserted, this, &SpreadsheetView::handleAspectsAdded); 0209 connect(m_spreadsheet, &Spreadsheet::aspectsAboutToBeRemoved, this, &SpreadsheetView::handleAspectAboutToBeRemoved); 0210 connect(m_spreadsheet, &Spreadsheet::requestProjectContextMenu, this, &SpreadsheetView::createContextMenu); 0211 connect(m_spreadsheet, &Spreadsheet::manyAspectsAboutToBeInserted, [this] { 0212 m_suppressResize = true; 0213 }); 0214 0215 // selection related connections 0216 connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged); 0217 connect(m_spreadsheet, &Spreadsheet::columnSelected, this, &SpreadsheetView::selectColumn); 0218 connect(m_spreadsheet, &Spreadsheet::columnDeselected, this, &SpreadsheetView::deselectColumn); 0219 } 0220 0221 /*! 0222 set the column sizes to the saved values or resize to content if no size was saved yet 0223 */ 0224 void SpreadsheetView::resizeHeader() { 0225 DEBUG(Q_FUNC_INFO) 0226 const auto& columns = m_spreadsheet->children<Column>(); 0227 0228 QFontMetrics fontMetrics(m_horizontalHeader->font()); 0229 const auto* style = m_horizontalHeader->style(); 0230 int headerOffset = style->pixelMetric(QStyle::PM_SmallIconSize, nullptr, m_horizontalHeader); // icon size 0231 headerOffset += 3 * style->pixelMetric(QStyle::PM_HeaderMargin, nullptr, m_horizontalHeader); // two margins plus the margin between icon and text 0232 0233 int c = 0; 0234 for (auto col : columns) { 0235 if (col->width() == 0) { 0236 // No width was saved yet, resize to fit the content: 0237 // Calling m_tableView->resizeColumnToContents(i) is expensive since Qt needs to iterate over all values in the column 0238 // and to determine the maximal length which takes time the more rows we have in the spreadsheet. For a high number of 0239 // columns in the spreadsheet this operation can take significant amount of time, s. a. BUG: 455977. 0240 // To improve the performance, we check the width of the header texts only and resize the column widths to fit the header 0241 // widths. There will be cases where this is not a perfect fit but the user still can resize manually if needed. 0242 // Since this approach doesn't always lead to precise results (text width in other cells in the column can be bigger) and 0243 // since in many cases we deal with much lower number of columns during the import, we apply the more precise method with 0244 // resizeColumnToContents() if the number of columns is smaller than 50. 0245 if (columns.count() > 50) { 0246 int width = headerOffset + fontMetrics.horizontalAdvance(m_model->headerData(c, Qt::Horizontal, Qt::DisplayRole).toString()); 0247 m_tableView->setColumnWidth(c, width); 0248 } else 0249 m_tableView->resizeColumnToContents(c); 0250 } else 0251 m_tableView->setColumnWidth(c, col->width()); 0252 0253 c++; 0254 } 0255 } 0256 0257 void SpreadsheetView::setFocus() { 0258 m_tableView->setFocus(); 0259 } 0260 0261 void SpreadsheetView::resizeEvent(QResizeEvent* event) { 0262 QWidget::resizeEvent(event); 0263 if (m_frozenTableView) 0264 updateFrozenTableGeometry(); 0265 } 0266 0267 void SpreadsheetView::updateFrozenTableGeometry() { 0268 m_frozenTableView->setGeometry(m_tableView->verticalHeader()->width() + m_tableView->frameWidth(), 0269 m_tableView->frameWidth(), 0270 m_tableView->columnWidth(0), 0271 m_tableView->viewport()->height() + m_tableView->horizontalHeader()->height()); 0272 } 0273 0274 void SpreadsheetView::initActions() { 0275 // selection related actions 0276 action_cut_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18n("Cu&t"), this); 0277 action_copy_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy"), this); 0278 action_paste_into_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Past&e"), this); 0279 action_mask_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-node")), i18n("&Mask"), this); 0280 action_unmask_selection = new QAction(QIcon::fromTheme(QStringLiteral("format-remove-node")), i18n("&Unmask"), this); 0281 action_clear_selection = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clea&r Content"), this); 0282 action_select_all = new QAction(QIcon::fromTheme(QStringLiteral("edit-select-all")), i18n("Select All"), this); 0283 0284 // action_set_formula = new QAction(QIcon::fromTheme(QString()), i18n("Assign &Formula"), this); 0285 // action_recalculate = new QAction(QIcon::fromTheme(QString()), i18n("Recalculate"), this); 0286 // action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); 0287 action_fill_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); 0288 action_fill_random = new QAction(QIcon::fromTheme(QString()), i18n("Uniform Random Values"), this); 0289 action_fill_random_nonuniform = new QAction(QIcon::fromTheme(QString()), i18n("Random Values"), this); 0290 action_fill_equidistant = new QAction(QIcon::fromTheme(QString()), i18n("Equidistant Values"), this); 0291 action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this); 0292 action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this); 0293 0294 action_sample_values = new QAction(QIcon::fromTheme(QStringLiteral("view-list-details")), i18n("Sample Values"), this); 0295 action_flatten_columns = new QAction(QIcon::fromTheme(QStringLiteral("gnumeric-object-list")), i18n("Flatten Columns"), this); 0296 0297 // spreadsheet related actions 0298 action_toggle_comments = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Show Comments"), this); 0299 action_clear_spreadsheet = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Spreadsheet"), this); 0300 action_clear_masks = new QAction(QIcon::fromTheme(QStringLiteral("format-remove-node")), i18n("Clear Masks"), this); 0301 action_go_to_cell = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("&Go to Cell..."), this); 0302 action_search = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Search"), this); 0303 action_search->setShortcut(QKeySequence::Find); 0304 action_search_replace = new QAction(QIcon::fromTheme(QStringLiteral("edit-find-replace")), i18n("&Replace"), this); 0305 action_search_replace->setShortcut(QKeySequence::Replace); 0306 action_statistics_all_columns = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Column Statistics..."), this); 0307 0308 action_statistics_spreadsheet = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Column Statistics Spreadsheet"), this); 0309 action_statistics_spreadsheet->setCheckable(true); 0310 bool hasStatisticsSpreadsheet = (m_spreadsheet->children<StatisticsSpreadsheet>().size() == 1); 0311 action_statistics_spreadsheet->setChecked(hasStatisticsSpreadsheet); 0312 0313 // column related actions 0314 action_insert_column_left = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-left")), i18n("Insert Column Left"), this); 0315 action_insert_column_right = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-right")), i18n("Insert Column Right"), this); 0316 action_insert_columns_left = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-left")), i18n("Insert Multiple Columns Left"), this); 0317 action_insert_columns_right = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-right")), i18n("Insert Multiple Columns Right"), this); 0318 action_remove_columns = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-column")), i18n("Delete Selected Column(s)"), this); 0319 action_clear_columns = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Content"), this); 0320 action_freeze_columns = new QAction(i18n("Freeze Column"), this); 0321 0322 // TODO: action collection? 0323 action_set_as_none = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::NoDesignation, false), this); 0324 action_set_as_none->setData(static_cast<int>(AbstractColumn::PlotDesignation::NoDesignation)); 0325 0326 action_set_as_x = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::X, false), this); 0327 action_set_as_x->setData(static_cast<int>(AbstractColumn::PlotDesignation::X)); 0328 0329 action_set_as_y = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::Y, false), this); 0330 action_set_as_y->setData(static_cast<int>(AbstractColumn::PlotDesignation::Y)); 0331 0332 action_set_as_z = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::Z, false), this); 0333 action_set_as_z->setData(static_cast<int>(AbstractColumn::PlotDesignation::Z)); 0334 0335 action_set_as_xerr = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::XError, false), this); 0336 action_set_as_xerr->setData(static_cast<int>(AbstractColumn::PlotDesignation::XError)); 0337 0338 action_set_as_xerr_plus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::XErrorPlus, false), this); 0339 action_set_as_xerr_plus->setData(static_cast<int>(AbstractColumn::PlotDesignation::XErrorPlus)); 0340 0341 action_set_as_xerr_minus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::XErrorMinus, false), this); 0342 action_set_as_xerr_minus->setData(static_cast<int>(AbstractColumn::PlotDesignation::XErrorMinus)); 0343 0344 action_set_as_yerr = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::YError, false), this); 0345 action_set_as_yerr->setData(static_cast<int>(AbstractColumn::PlotDesignation::YError)); 0346 0347 action_set_as_yerr_plus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::YErrorPlus, false), this); 0348 action_set_as_yerr_plus->setData(static_cast<int>(AbstractColumn::PlotDesignation::YErrorPlus)); 0349 0350 action_set_as_yerr_minus = new QAction(AbstractColumn::plotDesignationString(AbstractColumn::PlotDesignation::YErrorMinus, false), this); 0351 action_set_as_yerr_minus->setData(static_cast<int>(AbstractColumn::PlotDesignation::YErrorMinus)); 0352 0353 // data manipulation 0354 action_add_value = new QAction(i18n("Add"), this); 0355 action_add_value->setData(AddSubtractValueDialog::Add); 0356 action_subtract_value = new QAction(i18n("Subtract"), this); 0357 action_subtract_value->setData(AddSubtractValueDialog::Subtract); 0358 action_multiply_value = new QAction(i18n("Multiply"), this); 0359 action_multiply_value->setData(AddSubtractValueDialog::Multiply); 0360 action_divide_value = new QAction(i18n("Divide"), this); 0361 action_divide_value->setData(AddSubtractValueDialog::Divide); 0362 action_drop_values = new QAction(QIcon::fromTheme(QStringLiteral("delete-table-row")), i18n("Drop Values"), this); 0363 action_mask_values = new QAction(QIcon::fromTheme(QStringLiteral("hide_table_row")), i18n("Mask Values"), this); 0364 0365 action_reverse_columns = new QAction(QIcon::fromTheme(QStringLiteral("reverse")), i18n("Reverse"), this); 0366 // action_join_columns = new QAction(QIcon::fromTheme(QString()), i18n("Join"), this); 0367 0368 // algorithms - baseline subtraction, outliar removal, etc. 0369 action_subtract_baseline = new QAction(i18n("Subtract Baseline"), this); 0370 action_subtract_baseline->setData(AddSubtractValueDialog::SubtractBaseline); 0371 0372 // normalization 0373 normalizeColumnActionGroup = new QActionGroup(this); 0374 QAction* normalizeAction = new QAction(i18n("Divide by Sum"), normalizeColumnActionGroup); 0375 normalizeAction->setData(DivideBySum); 0376 0377 normalizeAction = new QAction(i18n("Divide by Min"), normalizeColumnActionGroup); 0378 normalizeAction->setData(DivideByMin); 0379 0380 normalizeAction = new QAction(i18n("Divide by Max"), normalizeColumnActionGroup); 0381 normalizeAction->setData(DivideByMax); 0382 0383 normalizeAction = new QAction(i18n("Divide by Count"), normalizeColumnActionGroup); 0384 normalizeAction->setData(DivideByCount); 0385 0386 normalizeAction = new QAction(i18n("Divide by Mean"), normalizeColumnActionGroup); 0387 normalizeAction->setData(DivideByMean); 0388 0389 normalizeAction = new QAction(i18n("Divide by Median"), normalizeColumnActionGroup); 0390 normalizeAction->setData(DivideByMedian); 0391 0392 normalizeAction = new QAction(i18n("Divide by Mode"), normalizeColumnActionGroup); 0393 normalizeAction->setData(DivideByMode); 0394 0395 normalizeAction = new QAction(i18n("Divide by Range"), normalizeColumnActionGroup); 0396 normalizeAction->setData(DivideByRange); 0397 0398 normalizeAction = new QAction(i18n("Divide by SD"), normalizeColumnActionGroup); 0399 normalizeAction->setData(DivideBySD); 0400 0401 normalizeAction = new QAction(i18n("Divide by MAD"), normalizeColumnActionGroup); 0402 normalizeAction->setData(DivideByMAD); 0403 0404 normalizeAction = new QAction(i18n("Divide by IQR"), normalizeColumnActionGroup); 0405 normalizeAction->setData(DivideByIQR); 0406 0407 normalizeAction = new QAction(i18n("(x-Mean)/SD"), normalizeColumnActionGroup); 0408 normalizeAction->setData(ZScoreSD); 0409 0410 normalizeAction = new QAction(i18n("(x-Median)/MAD"), normalizeColumnActionGroup); 0411 normalizeAction->setData(ZScoreMAD); 0412 0413 normalizeAction = new QAction(i18n("(x-Median)/IQR"), normalizeColumnActionGroup); 0414 normalizeAction->setData(ZScoreIQR); 0415 0416 normalizeAction = new QAction(i18n("Rescale to [a, b]"), normalizeColumnActionGroup); 0417 normalizeAction->setData(Rescale); 0418 0419 // action_normalize_selection = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize Selection"), this); 0420 0421 // Tukey's ladder of powers 0422 ladderOfPowersActionGroup = new QActionGroup(this); 0423 0424 QAction* ladderAction = new QAction(QStringLiteral("x³"), ladderOfPowersActionGroup); 0425 ladderAction->setData(Cube); 0426 0427 ladderAction = new QAction(QStringLiteral("x²"), ladderOfPowersActionGroup); 0428 ladderAction->setData(Squared); 0429 0430 ladderAction = new QAction(QStringLiteral("√x"), ladderOfPowersActionGroup); 0431 ladderAction->setData(SquareRoot); 0432 0433 ladderAction = new QAction(QLatin1String("log(x)"), ladderOfPowersActionGroup); 0434 ladderAction->setData(Log); 0435 0436 ladderAction = new QAction(QStringLiteral("1/√x"), ladderOfPowersActionGroup); 0437 ladderAction->setData(InverseSquareRoot); 0438 0439 ladderAction = new QAction(QLatin1String("1/x"), ladderOfPowersActionGroup); 0440 ladderAction->setData(Inverse); 0441 0442 ladderAction = new QAction(QStringLiteral("1/x²"), ladderOfPowersActionGroup); 0443 ladderAction->setData(InverseSquared); 0444 0445 // sort and statistics 0446 action_sort = new QAction(QIcon::fromTheme(QStringLiteral("view-sort")), i18n("&Sort..."), this); 0447 action_sort->setToolTip(i18n("Sort multiple columns together")); 0448 action_sort_asc = new QAction(QIcon::fromTheme(QStringLiteral("view-sort-ascending")), i18n("Sort &Ascending"), this); 0449 action_sort_asc->setToolTip(i18n("Sort the selected columns separately in ascending order")); 0450 action_sort_desc = new QAction(QIcon::fromTheme(QStringLiteral("view-sort-descending")), i18n("Sort &Descending"), this); 0451 action_sort_desc->setToolTip(i18n("Sort the selected columns separately in descending order")); 0452 action_statistics_columns = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Column Statistics..."), this); 0453 0454 // conditional formatting 0455 action_formatting_heatmap = new QAction(QIcon::fromTheme(QStringLiteral("color-management")), i18n("Heatmap"), this); 0456 action_formatting_remove = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Delete"), this); 0457 0458 // row related actions 0459 action_insert_row_above = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-above")), i18n("Insert Row Above"), this); 0460 action_insert_row_below = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Insert Row Below"), this); 0461 // TODO: setting of the following shortcut collides with the key press handling in the event filter 0462 // action_insert_row_below->setShortcut(Qt::Key_Insert); 0463 action_insert_rows_above = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-above")), i18n("Insert Multiple Rows Above"), this); 0464 action_insert_rows_below = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Insert Multiple Rows Below"), this); 0465 action_remove_rows = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Remo&ve Selected Row(s)"), this); 0466 action_remove_missing_value_rows = new QAction(QIcon::fromTheme(QStringLiteral("delete-table-row")), i18n("Delete Rows With Missing Values"), this); 0467 action_mask_missing_value_rows = new QAction(QIcon::fromTheme(QStringLiteral("hide_table_row")), i18n("Mask Rows With Missing Values"), this); 0468 action_statistics_rows = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Row Statisti&cs"), this); 0469 0470 // Analyze and plot menu actions 0471 addDataReductionAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Reduce Data"), this); 0472 // addDataReductionAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-data-reduction-curve")), i18n("Reduce Data"), this); 0473 addDataReductionAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::DataReduction)); 0474 addDifferentiationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Differentiate"), this); 0475 // addDifferentiationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-differentiation-curve")), i18n("Differentiate"), this); 0476 addDifferentiationAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Differentiation)); 0477 addIntegrationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("Integrate"), this); 0478 // addIntegrationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-integration-curve")), i18n("Integrate"), this); 0479 addIntegrationAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Integration)); 0480 addInterpolationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-interpolation-curve")), i18n("Interpolate"), this); 0481 addInterpolationAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Interpolation)); 0482 addSmoothAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-smoothing-curve")), i18n("Smooth"), this); 0483 addSmoothAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::Smoothing)); 0484 0485 QAction* fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Linear"), this); 0486 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitLinear)); 0487 addFitAction.append(fitAction); 0488 0489 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Power"), this); 0490 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitPower)); 0491 addFitAction.append(fitAction); 0492 0493 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Exponential (degree 1)"), this); 0494 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitExp1)); 0495 addFitAction.append(fitAction); 0496 0497 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Exponential (degree 2)"), this); 0498 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitExp2)); 0499 addFitAction.append(fitAction); 0500 0501 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Inverse Exponential"), this); 0502 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitInvExp)); 0503 addFitAction.append(fitAction); 0504 0505 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Gauss"), this); 0506 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitGauss)); 0507 addFitAction.append(fitAction); 0508 0509 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Cauchy-Lorentz"), this); 0510 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitCauchyLorentz)); 0511 addFitAction.append(fitAction); 0512 0513 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Arc Tangent"), this); 0514 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitTan)); 0515 addFitAction.append(fitAction); 0516 0517 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Hyperbolic Tangent"), this); 0518 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitTanh)); 0519 addFitAction.append(fitAction); 0520 0521 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Error Function"), this); 0522 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitErrFunc)); 0523 addFitAction.append(fitAction); 0524 0525 fitAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Custom"), this); 0526 fitAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FitCustom)); 0527 addFitAction.append(fitAction); 0528 0529 addFourierFilterAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-filter-curve")), i18n("Fourier Filter"), this); 0530 addFourierFilterAction->setData(static_cast<int>(XYAnalysisCurve::AnalysisAction::FourierFilter)); 0531 0532 connectActions(); 0533 } 0534 0535 void SpreadsheetView::initMenus() { 0536 initActions(); 0537 0538 // Selection menu 0539 m_selectionMenu = new QMenu(i18n("Selection"), this); 0540 m_selectionMenu->setIcon(QIcon::fromTheme(QStringLiteral("selection"))); 0541 connect(m_selectionMenu, &QMenu::aboutToShow, this, &SpreadsheetView::checkSpreadsheetSelectionMenu); 0542 0543 if (!m_readOnly) { 0544 // submenu = new QMenu(i18n("Fi&ll Selection With"), this); 0545 // submenu->setIcon(QIcon::fromTheme(QStringLiteral("select-rectangle"))); 0546 // submenu->addAction(action_fill_sel_row_numbers); 0547 // submenu->addAction(action_fill_const); 0548 // m_selectionMenu->addMenu(submenu); 0549 // m_selectionMenu->addSeparator(); 0550 m_selectionMenu->addAction(action_cut_selection); 0551 } 0552 0553 m_selectionMenu->addAction(action_copy_selection); 0554 0555 if (!m_readOnly) { 0556 m_selectionMenu->addAction(action_paste_into_selection); 0557 m_selectionMenu->addAction(action_clear_selection); 0558 m_selectionMenu->addSeparator(); 0559 m_selectionMenu->addAction(action_mask_selection); 0560 m_selectionMenu->addAction(action_unmask_selection); 0561 m_selectionMenu->addSeparator(); 0562 // m_selectionMenu->addAction(action_normalize_selection); 0563 } 0564 0565 // plot data menu 0566 plotDataActionGroup = new QActionGroup(this); 0567 connect(plotDataActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::plotData); 0568 m_plotDataMenu = new QMenu(i18n("Plot Data"), this); 0569 PlotDataDialog::fillMenu(m_plotDataMenu, plotDataActionGroup); 0570 0571 // conditional formatting 0572 m_formattingMenu = new QMenu(i18n("Conditional Formatting"), this); 0573 m_formattingMenu->addAction(action_formatting_heatmap); 0574 m_formattingMenu->addSeparator(); 0575 m_formattingMenu->addAction(action_formatting_remove); 0576 0577 // Column menu 0578 m_columnMenu = new QMenu(this); 0579 m_columnMenu->addMenu(m_plotDataMenu); 0580 0581 // Data fit sub-menu 0582 QMenu* dataFitMenu = new QMenu(i18n("Fit"), this); 0583 dataFitMenu->setIcon(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve"))); 0584 dataFitMenu->addAction(addFitAction.at(0)); 0585 dataFitMenu->addAction(addFitAction.at(1)); 0586 dataFitMenu->addAction(addFitAction.at(2)); 0587 dataFitMenu->addAction(addFitAction.at(3)); 0588 dataFitMenu->addAction(addFitAction.at(4)); 0589 dataFitMenu->addSeparator(); 0590 dataFitMenu->addAction(addFitAction.at(5)); 0591 dataFitMenu->addAction(addFitAction.at(6)); 0592 dataFitMenu->addSeparator(); 0593 dataFitMenu->addAction(addFitAction.at(7)); 0594 dataFitMenu->addAction(addFitAction.at(8)); 0595 dataFitMenu->addAction(addFitAction.at(9)); 0596 dataFitMenu->addSeparator(); 0597 dataFitMenu->addAction(addFitAction.at(10)); 0598 0599 // analyze and plot data menu 0600 m_analyzePlotMenu = new QMenu(i18n("Analyze and Plot Data"), this); 0601 m_analyzePlotMenu->addMenu(dataFitMenu); 0602 m_analyzePlotMenu->addSeparator(); 0603 m_analyzePlotMenu->addAction(addDifferentiationAction); 0604 m_analyzePlotMenu->addAction(addIntegrationAction); 0605 m_analyzePlotMenu->addSeparator(); 0606 m_analyzePlotMenu->addAction(addInterpolationAction); 0607 m_analyzePlotMenu->addAction(addSmoothAction); 0608 m_analyzePlotMenu->addSeparator(); 0609 m_analyzePlotMenu->addAction(addFourierFilterAction); 0610 m_analyzePlotMenu->addSeparator(); 0611 m_analyzePlotMenu->addAction(addDataReductionAction); 0612 m_columnMenu->addMenu(m_analyzePlotMenu); 0613 0614 m_columnSetAsMenu = new QMenu(i18n("Set Column As"), this); 0615 m_columnMenu->addSeparator(); 0616 m_columnSetAsMenu->addAction(action_set_as_x); 0617 m_columnSetAsMenu->addAction(action_set_as_y); 0618 m_columnSetAsMenu->addAction(action_set_as_z); 0619 m_columnSetAsMenu->addSeparator(); 0620 m_columnSetAsMenu->addAction(action_set_as_xerr); 0621 m_columnSetAsMenu->addAction(action_set_as_xerr_plus); 0622 m_columnSetAsMenu->addAction(action_set_as_xerr_minus); 0623 m_columnSetAsMenu->addSeparator(); 0624 m_columnSetAsMenu->addAction(action_set_as_yerr); 0625 m_columnSetAsMenu->addAction(action_set_as_yerr_plus); 0626 m_columnSetAsMenu->addAction(action_set_as_yerr_minus); 0627 m_columnSetAsMenu->addSeparator(); 0628 m_columnSetAsMenu->addAction(action_set_as_none); 0629 m_columnMenu->addMenu(m_columnSetAsMenu); 0630 0631 if (!m_readOnly) { 0632 m_columnGenerateDataMenu = new QMenu(i18n("Generate Data"), this); 0633 m_columnGenerateDataMenu->addAction(action_fill_row_numbers); 0634 m_columnGenerateDataMenu->addAction(action_fill_const); 0635 m_columnGenerateDataMenu->addSeparator(); 0636 m_columnGenerateDataMenu->addAction(action_fill_equidistant); 0637 m_columnGenerateDataMenu->addAction(action_fill_random_nonuniform); 0638 m_columnGenerateDataMenu->addAction(action_fill_function); 0639 m_columnGenerateDataMenu->addSeparator(); 0640 m_columnGenerateDataMenu->addAction(action_sample_values); 0641 m_columnGenerateDataMenu->addAction(action_flatten_columns); 0642 0643 m_columnMenu->addSeparator(); 0644 m_columnMenu->addMenu(m_columnGenerateDataMenu); 0645 m_columnMenu->addSeparator(); 0646 0647 m_columnManipulateDataMenu = new QMenu(i18n("Manipulate Data"), this); 0648 m_columnManipulateDataMenu->addAction(action_add_value); 0649 m_columnManipulateDataMenu->addAction(action_subtract_value); 0650 m_columnManipulateDataMenu->addAction(action_multiply_value); 0651 m_columnManipulateDataMenu->addAction(action_divide_value); 0652 m_columnManipulateDataMenu->addSeparator(); 0653 m_columnManipulateDataMenu->addAction(action_subtract_baseline); 0654 m_columnManipulateDataMenu->addSeparator(); 0655 m_columnManipulateDataMenu->addAction(action_reverse_columns); 0656 m_columnManipulateDataMenu->addSeparator(); 0657 m_columnManipulateDataMenu->addAction(action_drop_values); 0658 m_columnManipulateDataMenu->addAction(action_mask_values); 0659 m_columnManipulateDataMenu->addSeparator(); 0660 // m_columnManipulateDataMenu->addAction(action_join_columns); 0661 0662 // normalization menu with the following structure 0663 // Divide by Sum 0664 // Divide by Min 0665 // Divide by Max 0666 // Divide by Count 0667 //-------------- 0668 // Divide by Mean 0669 // Divide by Median 0670 // Divide by Mode 0671 //--------------- 0672 // Divide by Range 0673 // Divide by SD 0674 // Divide by MAD 0675 // Divide by IQR 0676 //-------------- 0677 //(x-Mean)/SD 0678 //(x-Median)/MAD 0679 //(x-Median)/IQR 0680 //-------------- 0681 // Rescale to [a, b] 0682 0683 m_columnNormalizeMenu = new QMenu(i18n("Normalize"), this); 0684 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(0)); 0685 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(1)); 0686 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(2)); 0687 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(3)); 0688 m_columnNormalizeMenu->addSeparator(); 0689 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(4)); 0690 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(5)); 0691 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(6)); 0692 m_columnNormalizeMenu->addSeparator(); 0693 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(7)); 0694 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(8)); 0695 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(9)); 0696 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(10)); 0697 m_columnNormalizeMenu->addSeparator(); 0698 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(11)); 0699 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(12)); 0700 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(13)); 0701 m_columnNormalizeMenu->addSeparator(); 0702 m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(14)); 0703 m_columnManipulateDataMenu->addMenu(m_columnNormalizeMenu); 0704 0705 //"Ladder of powers" transformation 0706 m_columnLadderOfPowersMenu = new QMenu(i18n("Ladder of Powers"), this); 0707 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(0)); 0708 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(1)); 0709 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(2)); 0710 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(3)); 0711 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(4)); 0712 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(5)); 0713 m_columnLadderOfPowersMenu->addAction(ladderOfPowersActionGroup->actions().at(6)); 0714 0715 m_columnManipulateDataMenu->addSeparator(); 0716 m_columnManipulateDataMenu->addMenu(m_columnLadderOfPowersMenu); 0717 0718 m_columnMenu->addMenu(m_columnManipulateDataMenu); 0719 m_columnMenu->addSeparator(); 0720 0721 m_columnMenu->addAction(action_sort); 0722 m_columnMenu->addAction(action_sort_asc); 0723 m_columnMenu->addAction(action_sort_desc); 0724 } 0725 0726 m_columnMenu->addSeparator(); 0727 m_columnMenu->addMenu(m_formattingMenu); 0728 m_columnMenu->addSeparator(); 0729 m_columnMenu->addAction(action_freeze_columns); 0730 m_columnMenu->addSeparator(); 0731 m_columnMenu->addAction(action_toggle_comments); 0732 m_columnMenu->addSeparator(); 0733 m_columnMenu->addAction(action_statistics_columns); 0734 0735 if (!m_readOnly) { 0736 m_columnMenu->addSeparator(); 0737 m_columnMenu->addAction(action_insert_column_left); 0738 m_columnMenu->addAction(action_insert_column_right); 0739 m_columnMenu->addSeparator(); 0740 m_columnMenu->addAction(action_insert_columns_left); 0741 m_columnMenu->addAction(action_insert_columns_right); 0742 m_columnMenu->addSeparator(); 0743 m_columnMenu->addAction(action_remove_columns); 0744 m_columnMenu->addAction(action_clear_columns); 0745 m_columnMenu->addSeparator(); 0746 m_columnMenu->addAction(action_remove_missing_value_rows); 0747 m_columnMenu->addAction(action_mask_missing_value_rows); 0748 } 0749 0750 // Spreadsheet menu 0751 m_spreadsheetMenu = new QMenu(this); 0752 createContextMenu(m_spreadsheetMenu); 0753 0754 // Row menu 0755 m_rowMenu = new QMenu(this); 0756 if (!m_readOnly) { 0757 // submenu = new QMenu(i18n("Fi&ll Selection With"), this); 0758 // submenu->addAction(action_fill_sel_row_numbers); 0759 // submenu->addAction(action_fill_const); 0760 // m_rowMenu->addMenu(submenu); 0761 // m_rowMenu->addSeparator(); 0762 0763 m_rowMenu->addAction(action_insert_row_above); 0764 m_rowMenu->addAction(action_insert_row_below); 0765 m_rowMenu->addSeparator(); 0766 0767 m_rowMenu->addAction(action_insert_rows_above); 0768 m_rowMenu->addAction(action_insert_rows_below); 0769 m_rowMenu->addSeparator(); 0770 0771 m_rowMenu->addAction(action_remove_rows); 0772 m_rowMenu->addAction(action_clear_selection); 0773 m_rowMenu->addSeparator(); 0774 0775 m_rowMenu->addAction(action_remove_missing_value_rows); 0776 m_rowMenu->addAction(action_mask_missing_value_rows); 0777 } 0778 m_rowMenu->addSeparator(); 0779 m_rowMenu->addAction(action_statistics_rows); 0780 } 0781 0782 void SpreadsheetView::connectActions() { 0783 connect(action_cut_selection, &QAction::triggered, this, &SpreadsheetView::cutSelection); 0784 connect(action_copy_selection, &QAction::triggered, this, &SpreadsheetView::copySelection); 0785 connect(action_paste_into_selection, &QAction::triggered, this, &SpreadsheetView::pasteIntoSelection); 0786 connect(action_mask_selection, &QAction::triggered, this, &SpreadsheetView::maskSelection); 0787 connect(action_unmask_selection, &QAction::triggered, this, &SpreadsheetView::unmaskSelection); 0788 0789 connect(action_clear_selection, &QAction::triggered, this, &SpreadsheetView::clearSelectedCells); 0790 // connect(action_recalculate, &QAction::triggered, this, &SpreadsheetView::recalculateSelectedCells); 0791 connect(action_fill_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillWithRowNumbers); 0792 // connect(action_fill_sel_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRowNumbers); 0793 // connect(action_fill_random, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRandomNumbers); 0794 connect(action_fill_random_nonuniform, &QAction::triggered, this, &SpreadsheetView::fillWithRandomValues); 0795 connect(action_fill_equidistant, &QAction::triggered, this, &SpreadsheetView::fillWithEquidistantValues); 0796 connect(action_fill_function, &QAction::triggered, this, &SpreadsheetView::fillWithFunctionValues); 0797 connect(action_fill_const, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithConstValues); 0798 connect(action_select_all, &QAction::triggered, m_tableView, &QTableView::selectAll); 0799 connect(action_clear_spreadsheet, &QAction::triggered, m_spreadsheet, QOverload<>::of(&Spreadsheet::clear)); 0800 connect(action_clear_masks, &QAction::triggered, m_spreadsheet, &Spreadsheet::clearMasks); 0801 connect(action_go_to_cell, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::goToCell)); 0802 connect(action_search, &QAction::triggered, this, &SpreadsheetView::searchReplace); 0803 connect(action_search_replace, &QAction::triggered, this, &SpreadsheetView::searchReplace); 0804 0805 connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft); 0806 connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight); 0807 connect(action_insert_columns_left, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertColumnsLeft)); 0808 connect(action_insert_columns_right, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertColumnsRight)); 0809 connect(action_remove_columns, &QAction::triggered, this, &SpreadsheetView::removeSelectedColumns); 0810 connect(action_clear_columns, &QAction::triggered, this, &SpreadsheetView::clearSelectedColumns); 0811 connect(action_freeze_columns, &QAction::triggered, this, &SpreadsheetView::toggleFreezeColumn); 0812 connect(action_set_as_none, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0813 connect(action_set_as_x, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0814 connect(action_set_as_y, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0815 connect(action_set_as_z, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0816 connect(action_set_as_xerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0817 connect(action_set_as_xerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0818 connect(action_set_as_xerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0819 connect(action_set_as_yerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0820 connect(action_set_as_yerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0821 connect(action_set_as_yerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); 0822 0823 // data manipulation 0824 connect(action_add_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); 0825 connect(action_subtract_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); 0826 connect(action_multiply_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); 0827 connect(action_divide_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); 0828 connect(action_reverse_columns, &QAction::triggered, this, &SpreadsheetView::reverseColumns); 0829 connect(action_drop_values, &QAction::triggered, this, &SpreadsheetView::dropColumnValues); 0830 connect(action_mask_values, &QAction::triggered, this, &SpreadsheetView::maskColumnValues); 0831 connect(action_sample_values, &QAction::triggered, this, &SpreadsheetView::sampleColumnValues); 0832 connect(action_flatten_columns, &QAction::triggered, this, &SpreadsheetView::flattenColumns); 0833 0834 // algorithms 0835 connect(action_subtract_baseline, &QAction::triggered, this, &SpreadsheetView::modifyValues); 0836 0837 // connect(action_join_columns, &QAction::triggered, this, &SpreadsheetView::joinColumns); 0838 connect(normalizeColumnActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::normalizeSelectedColumns); 0839 connect(ladderOfPowersActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::powerTransformSelectedColumns); 0840 // connect(action_normalize_selection, &QAction::triggered, this, &SpreadsheetView::normalizeSelection); 0841 0842 // sort 0843 connect(action_sort, &QAction::triggered, this, &SpreadsheetView::sortCustom); 0844 connect(action_sort_asc, &QAction::triggered, this, &SpreadsheetView::sortAscending); 0845 connect(action_sort_desc, &QAction::triggered, this, &SpreadsheetView::sortDescending); 0846 0847 // conditional formatting 0848 connect(action_formatting_heatmap, &QAction::triggered, this, &SpreadsheetView::formatHeatmap); 0849 connect(action_formatting_remove, &QAction::triggered, this, &SpreadsheetView::removeFormat); 0850 0851 // statistics 0852 connect(action_statistics_columns, &QAction::triggered, this, &SpreadsheetView::showColumnStatistics); 0853 connect(action_statistics_all_columns, &QAction::triggered, this, &SpreadsheetView::showAllColumnsStatistics); 0854 connect(action_statistics_spreadsheet, &QAction::toggled, m_spreadsheet, &Spreadsheet::toggleStatisticsSpreadsheet); 0855 0856 connect(action_insert_row_above, &QAction::triggered, this, &SpreadsheetView::insertRowAbove); 0857 connect(action_insert_row_below, &QAction::triggered, this, &SpreadsheetView::insertRowBelow); 0858 connect(action_insert_rows_above, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertRowsAbove)); 0859 connect(action_insert_rows_below, &QAction::triggered, this, static_cast<void (SpreadsheetView::*)()>(&SpreadsheetView::insertRowsBelow)); 0860 connect(action_remove_rows, &QAction::triggered, this, &SpreadsheetView::removeSelectedRows); 0861 connect(action_remove_missing_value_rows, &QAction::triggered, m_spreadsheet, &Spreadsheet::removeEmptyRows); 0862 connect(action_mask_missing_value_rows, &QAction::triggered, m_spreadsheet, &Spreadsheet::maskEmptyRows); 0863 connect(action_statistics_rows, &QAction::triggered, this, &SpreadsheetView::showRowStatistics); 0864 connect(action_toggle_comments, &QAction::triggered, this, &SpreadsheetView::toggleComments); 0865 0866 connect(addDataReductionAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0867 connect(addDifferentiationAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0868 connect(addIntegrationAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0869 connect(addInterpolationAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0870 connect(addSmoothAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0871 for (const auto& action : addFitAction) 0872 connect(action, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0873 connect(addFourierFilterAction, &QAction::triggered, this, &SpreadsheetView::plotAnalysisData); 0874 } 0875 0876 void SpreadsheetView::fillToolBar(QToolBar* toolBar) { 0877 if (!m_readOnly) { 0878 toolBar->addAction(action_insert_row_above); 0879 toolBar->addAction(action_insert_row_below); 0880 toolBar->addAction(action_remove_rows); 0881 } 0882 // toolBar->addAction(action_statistics_rows); // TODO: the status enabled or not needs to be synchronized with the current selection (has valid numeric 0883 // values?) 0884 toolBar->addSeparator(); 0885 if (!m_readOnly) { 0886 toolBar->addAction(action_insert_column_left); 0887 toolBar->addAction(action_insert_column_right); 0888 toolBar->addAction(action_remove_columns); 0889 } 0890 0891 // toolBar->addAction(action_statistics_columns); 0892 if (!m_readOnly) { 0893 toolBar->addSeparator(); 0894 toolBar->addAction(action_sort); 0895 toolBar->addAction(action_sort_asc); 0896 toolBar->addAction(action_sort_desc); 0897 } 0898 } 0899 0900 #ifdef HAVE_TOUCHBAR 0901 void SpreadsheetView::fillTouchBar(KDMacTouchBar* touchBar) { 0902 // touchBar->addAction(action_insert_column_right); 0903 } 0904 #endif 0905 0906 /*! 0907 * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions. 0908 * The menu is used 0909 * - as the context menu in SpreadsheetView 0910 * - as the "spreadsheet menu" in the main menu-bar (called form MainWin) 0911 * - as a part of the spreadsheet context menu in project explorer 0912 */ 0913 void SpreadsheetView::createContextMenu(QMenu* menu) { 0914 Q_ASSERT(menu); 0915 0916 if (!m_selectionMenu) 0917 initMenus(); 0918 0919 checkSpreadsheetMenu(); 0920 0921 QAction* firstAction = nullptr; 0922 // if we're populating the context menu for the project explorer, then 0923 // there're already actions available there. Skip the first title-action 0924 // and insert the action at the beginning of the menu. 0925 if (menu->actions().size() > 1) 0926 firstAction = menu->actions().at(1); 0927 0928 if (m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0) { 0929 menu->insertMenu(firstAction, m_plotDataMenu); 0930 menu->insertMenu(firstAction, m_analyzePlotMenu); 0931 menu->insertSeparator(firstAction); 0932 } 0933 menu->insertMenu(firstAction, m_selectionMenu); 0934 menu->insertSeparator(firstAction); 0935 menu->insertAction(firstAction, action_select_all); 0936 if (!m_readOnly) { 0937 menu->insertAction(firstAction, action_clear_spreadsheet); 0938 menu->insertAction(firstAction, action_clear_masks); 0939 menu->insertAction(firstAction, action_sort); 0940 menu->insertSeparator(firstAction); 0941 } 0942 0943 menu->insertMenu(firstAction, m_formattingMenu); 0944 menu->insertSeparator(firstAction); 0945 menu->insertAction(firstAction, action_go_to_cell); 0946 menu->insertAction(firstAction, action_search); 0947 if (!m_readOnly) 0948 menu->insertAction(firstAction, action_search_replace); 0949 menu->insertSeparator(firstAction); 0950 menu->insertAction(firstAction, action_toggle_comments); 0951 menu->insertSeparator(firstAction); 0952 menu->insertAction(firstAction, action_statistics_spreadsheet); 0953 menu->insertSeparator(firstAction); 0954 menu->insertAction(firstAction, action_statistics_all_columns); 0955 menu->insertSeparator(firstAction); 0956 } 0957 0958 /*! 0959 * adds column specific actions in SpreadsheetView to the context menu shown in the project explorer. 0960 */ 0961 void SpreadsheetView::fillColumnContextMenu(QMenu* menu, Column* column) { 0962 if (!column) 0963 return; // should never happen, since the sender is always a Column 0964 0965 if (!m_selectionMenu) 0966 initMenus(); 0967 0968 const bool numeric = column->isNumeric(); 0969 const bool datetime = (column->columnMode() == AbstractColumn::ColumnMode::DateTime); 0970 0971 QAction* firstAction = menu->actions().at(1); 0972 menu->insertMenu(firstAction, m_plotDataMenu); 0973 menu->insertMenu(firstAction, m_analyzePlotMenu); 0974 menu->insertSeparator(firstAction); 0975 0976 if (numeric) 0977 menu->insertMenu(firstAction, m_columnSetAsMenu); 0978 0979 if (!m_readOnly) { 0980 if (numeric) { 0981 menu->insertSeparator(firstAction); 0982 menu->insertMenu(firstAction, m_columnGenerateDataMenu); 0983 menu->insertSeparator(firstAction); 0984 } 0985 0986 if (numeric || datetime) { 0987 menu->insertMenu(firstAction, m_columnManipulateDataMenu); 0988 menu->insertSeparator(firstAction); 0989 } 0990 0991 menu->insertAction(firstAction, action_sort); 0992 menu->insertAction(firstAction, action_sort_asc); 0993 menu->insertAction(firstAction, action_sort_desc); 0994 } 0995 0996 menu->insertSeparator(firstAction); 0997 menu->insertMenu(firstAction, m_formattingMenu); 0998 menu->insertSeparator(firstAction); 0999 menu->insertAction(firstAction, action_freeze_columns); 1000 menu->insertSeparator(firstAction); 1001 menu->insertAction(firstAction, action_toggle_comments); 1002 menu->insertSeparator(firstAction); 1003 menu->insertAction(firstAction, action_statistics_columns); 1004 1005 checkColumnMenus(QVector<Column*>{column}); 1006 } 1007 1008 // SLOTS 1009 void SpreadsheetView::handleAspectsAdded(int first, int last) { 1010 if (m_suppressResize) { 1011 m_suppressResize = false; 1012 return; 1013 } 1014 PERFTRACE(QLatin1String(Q_FUNC_INFO)); 1015 1016 const auto& children = m_spreadsheet->children<Column>(); 1017 1018 for (int i = first; i <= last; i++) { 1019 const auto* col = children.at(i); 1020 // TODO: this makes it slow! 1021 if (col->width() == 0) 1022 m_tableView->resizeColumnToContents(i); 1023 else 1024 m_tableView->setColumnWidth(i, col->width()); 1025 } 1026 1027 goToCell(0, last); 1028 } 1029 1030 void SpreadsheetView::handleAspectAboutToBeRemoved(int first, int last) { 1031 const auto& children = m_spreadsheet->children<Column>(); 1032 if (first < 0 || first >= children.count() || last >= children.count() || first > last) 1033 return; 1034 1035 for (int i = first; i <= last; i++) 1036 disconnect(children.at(i), nullptr, this, nullptr); 1037 } 1038 1039 void SpreadsheetView::handleHorizontalSectionResized(int logicalIndex, int /* oldSize */, int newSize) { 1040 // save the new size in the column 1041 Column* col = m_spreadsheet->child<Column>(logicalIndex); 1042 col->setWidth(newSize); 1043 1044 if (m_frozenTableView && logicalIndex == 0) { 1045 m_frozenTableView->setColumnWidth(0, newSize); 1046 updateFrozenTableGeometry(); 1047 } 1048 } 1049 1050 void SpreadsheetView::goToCell(int row, int col) { 1051 QModelIndex index = m_model->index(row, col); 1052 m_tableView->scrollTo(index); 1053 m_tableView->setCurrentIndex(index); 1054 } 1055 1056 void SpreadsheetView::selectCell(int row, int col) { 1057 m_tableView->selectionModel()->select(m_model->index(row, col), QItemSelectionModel::Select); 1058 } 1059 1060 void SpreadsheetView::clearSelection() { 1061 m_tableView->selectionModel()->clear(); 1062 } 1063 1064 void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) { 1065 static bool inside = false; 1066 if (inside) 1067 return; 1068 1069 Q_ASSERT(index == from); 1070 1071 inside = true; 1072 m_tableView->horizontalHeader()->moveSection(to, from); 1073 inside = false; 1074 m_spreadsheet->moveColumn(from, to); 1075 } 1076 1077 // TODO Implement the "change of the column name"-mode upon a double click 1078 void SpreadsheetView::handleHorizontalHeaderDoubleClicked(int /*index*/) { 1079 } 1080 1081 /*! 1082 Returns whether comments are shown currently or not 1083 */ 1084 bool SpreadsheetView::areCommentsShown() const { 1085 return m_horizontalHeader->areCommentsShown(); 1086 } 1087 1088 /*! 1089 toggles the column comment in the horizontal header 1090 */ 1091 void SpreadsheetView::toggleComments() { 1092 showComments(!areCommentsShown()); 1093 // TODO 1094 if (areCommentsShown()) 1095 action_toggle_comments->setText(i18n("Hide Comments")); 1096 else 1097 action_toggle_comments->setText(i18n("Show Comments")); 1098 } 1099 1100 //! Shows (\c on=true) or hides (\c on=false) the column comments in the horizontal header 1101 void SpreadsheetView::showComments(bool on) { 1102 m_horizontalHeader->showComments(on); 1103 } 1104 1105 void SpreadsheetView::handleHeaderDataChanged(Qt::Orientation orientation, int first, int last) { 1106 if (orientation != Qt::Horizontal) 1107 return; 1108 1109 for (int index = first; index <= last; ++index) 1110 m_tableView->resizeColumnToContents(index); 1111 } 1112 1113 /*! 1114 * return the selection model of the tree view, private function wrapper to be able 1115 * to unit-test the selection without exposing the whole internal table view. 1116 */ 1117 QItemSelectionModel* SpreadsheetView::selectionModel() { 1118 return m_tableView->selectionModel(); 1119 } 1120 1121 /*! 1122 Returns the number of selected columns. 1123 If \c full is \c true, this function only returns the number of fully selected columns. 1124 */ 1125 int SpreadsheetView::selectedColumnCount(bool full) const { 1126 int count = 0; 1127 const int cols = m_spreadsheet->columnCount(); 1128 for (int i = 0; i < cols; i++) 1129 if (isColumnSelected(i, full)) 1130 count++; 1131 return count; 1132 } 1133 1134 int SpreadsheetView::selectedRowCount(bool full) const { 1135 if (full) 1136 return m_tableView->selectionModel()->selectedRows().count(); 1137 const auto& indexes = m_tableView->selectionModel()->selectedIndexes(); 1138 QSet<int> set; 1139 for (auto& index : indexes) 1140 set.insert(index.row()); 1141 1142 return set.count(); 1143 } 1144 1145 /*! 1146 Returns the number of (at least partly) selected columns with the plot designation \param pd . 1147 */ 1148 int SpreadsheetView::selectedColumnCount(AbstractColumn::PlotDesignation pd) const { 1149 int count = 0; 1150 const int cols = m_spreadsheet->columnCount(); 1151 for (int i = 0; i < cols; i++) 1152 if (isColumnSelected(i, false) && (m_spreadsheet->column(i)->plotDesignation() == pd)) 1153 count++; 1154 1155 return count; 1156 } 1157 1158 /*! 1159 Returns \c true if column \param col is selected, otherwise returns \c false. 1160 If \param full is \c true, this function only returns true if the whole column is selected. 1161 */ 1162 bool SpreadsheetView::isColumnSelected(int col, bool full) const { 1163 if (full) 1164 return m_tableView->selectionModel()->isColumnSelected(col); 1165 else 1166 return m_tableView->selectionModel()->columnIntersectsSelection(col); 1167 } 1168 1169 /*! 1170 Returns all selected columns. 1171 If \param full is true, this function only returns a column if the whole column is selected. 1172 */ 1173 QVector<Column*> SpreadsheetView::selectedColumns(bool full) const { 1174 QVector<Column*> columns; 1175 const int cols = m_spreadsheet->columnCount(); 1176 for (int i = 0; i < cols; i++) 1177 if (isColumnSelected(i, full)) 1178 columns << m_spreadsheet->column(i); 1179 1180 return columns; 1181 } 1182 1183 /*! 1184 Returns \c true if row \param row is selected; otherwise returns \c false 1185 If \param full is \c true, this function only returns \c true if the whole row is selected. 1186 */ 1187 bool SpreadsheetView::isRowSelected(int row, bool full) const { 1188 if (full) 1189 return m_tableView->selectionModel()->isRowSelected(row); 1190 else 1191 return m_tableView->selectionModel()->rowIntersectsSelection(row); 1192 } 1193 1194 /*! 1195 Return the index of the first selected column. 1196 If \param full is \c true, this function only looks for fully selected columns. 1197 */ 1198 int SpreadsheetView::firstSelectedColumn(bool full) const { 1199 const int cols = m_spreadsheet->columnCount(); 1200 for (int i = 0; i < cols; i++) { 1201 if (isColumnSelected(i, full)) 1202 return i; 1203 } 1204 return -1; 1205 } 1206 1207 /*! 1208 Return the index of the last selected column. 1209 If \param full is \c true, this function only looks for fully selected columns. 1210 */ 1211 int SpreadsheetView::lastSelectedColumn(bool full) const { 1212 const int cols = m_spreadsheet->columnCount(); 1213 for (int i = cols - 1; i >= 0; i--) 1214 if (isColumnSelected(i, full)) 1215 return i; 1216 1217 return -2; 1218 } 1219 1220 /*! 1221 Return the index of the first selected row. 1222 If \param full is \c true, this function only looks for fully selected rows. 1223 */ 1224 int SpreadsheetView::firstSelectedRow(bool full) const { 1225 QModelIndexList indexes; 1226 if (!full) 1227 indexes = m_tableView->selectionModel()->selectedIndexes(); 1228 else 1229 indexes = m_tableView->selectionModel()->selectedRows(); 1230 1231 if (!indexes.empty()) 1232 return indexes.first().row(); 1233 else 1234 return -1; 1235 } 1236 1237 /*! 1238 Return the index of the last selected row. 1239 If \param full is \c true, this function only looks for fully selected rows. 1240 */ 1241 int SpreadsheetView::lastSelectedRow(bool full) const { 1242 QModelIndexList indexes; 1243 if (!full) 1244 indexes = m_tableView->selectionModel()->selectedIndexes(); 1245 else 1246 indexes = m_tableView->selectionModel()->selectedRows(); 1247 1248 if (!indexes.empty()) 1249 return indexes.last().row(); 1250 else 1251 return -2; 1252 } 1253 1254 /*! 1255 Return whether a cell is selected 1256 */ 1257 bool SpreadsheetView::isCellSelected(int row, int col) const { 1258 if (row < 0 || col < 0 || row >= m_spreadsheet->rowCount() || col >= m_spreadsheet->columnCount()) 1259 return false; 1260 1261 return m_tableView->selectionModel()->isSelected(m_model->index(row, col)); 1262 } 1263 1264 /*! 1265 Get the complete set of selected rows. 1266 */ 1267 IntervalAttribute<bool> SpreadsheetView::selectedRows(bool full) const { 1268 IntervalAttribute<bool> result; 1269 const int rows = m_spreadsheet->rowCount(); 1270 for (int i = 0; i < rows; i++) 1271 if (isRowSelected(i, full)) 1272 result.setValue(i, true); 1273 return result; 1274 } 1275 1276 /*! 1277 Select/Deselect a cell. 1278 */ 1279 void SpreadsheetView::setCellSelected(int row, int col, bool select) { 1280 m_tableView->selectionModel()->select(m_model->index(row, col), select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); 1281 } 1282 1283 /*! 1284 Select/Deselect a range of cells. 1285 */ 1286 void SpreadsheetView::setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select) { 1287 QModelIndex top_left = m_model->index(first_row, first_col); 1288 QModelIndex bottom_right = m_model->index(last_row, last_col); 1289 m_tableView->selectionModel()->select(QItemSelection(top_left, bottom_right), select ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Deselect); 1290 } 1291 1292 /*! 1293 Determine the current cell (-1 if no cell is designated as the current). 1294 */ 1295 void SpreadsheetView::getCurrentCell(int* row, int* col) const { 1296 QModelIndex index = m_tableView->selectionModel()->currentIndex(); 1297 if (index.isValid()) { 1298 *row = index.row(); 1299 *col = index.column(); 1300 } else { 1301 *row = -1; 1302 *col = -1; 1303 } 1304 } 1305 1306 bool SpreadsheetView::eventFilter(QObject* watched, QEvent* event) { 1307 if (event->type() == QEvent::ContextMenu) { 1308 // initialize all menus and actions if not done yet 1309 if (!m_plotDataMenu) 1310 initMenus(); 1311 1312 auto* cm_event = static_cast<QContextMenuEvent*>(event); 1313 const QPoint global_pos = cm_event->globalPos(); 1314 1315 if (watched == m_tableView->verticalHeader()) { 1316 bool numeric = true; 1317 const auto& columns = m_spreadsheet->children<Column>(); 1318 for (const auto* col : columns) { 1319 if (!col->isNumeric()) { 1320 numeric = false; 1321 break; 1322 } 1323 } 1324 1325 int hasValues = 0; 1326 if (numeric) { 1327 const auto& rows = m_tableView->selectionModel()->selectedRows(); 1328 for (int i = 0; i < rows.count(); ++i) { 1329 int row = rows.at(i).row(); 1330 1331 for (int j = 0; j < m_spreadsheet->columnCount(); ++j) { 1332 hasValues += !std::isnan(m_spreadsheet->column(j)->valueAt(row)); 1333 if (hasValues > 1) 1334 break; 1335 } 1336 1337 if (hasValues > 1) 1338 break; 1339 } 1340 } 1341 1342 action_statistics_rows->setEnabled(hasValues > 1); 1343 m_rowMenu->exec(global_pos); 1344 } else if ((watched == m_horizontalHeader) || (m_frozenTableView && watched == m_frozenTableView->horizontalHeader()) || !selectedColumns().isEmpty()) { 1345 // if the horizontal header was clicked, select the column under the cursor if not selected yet 1346 if (watched == m_horizontalHeader) { 1347 const int col = m_horizontalHeader->logicalIndexAt(cm_event->pos()); 1348 if (!isColumnSelected(col, true)) { 1349 auto* sel_model = m_tableView->selectionModel(); 1350 sel_model->clearSelection(); 1351 QItemSelection selection(m_model->index(0, col, QModelIndex()), m_model->index(m_model->rowCount() - 1, col, QModelIndex())); 1352 sel_model->select(selection, QItemSelectionModel::Select); 1353 } 1354 } 1355 1356 checkColumnMenus(selectedColumns()); 1357 m_columnMenu->exec(global_pos); 1358 } else if (watched == this) { 1359 // the cursor position is in one of the cells and no full columns are selected, 1360 // show the global spreadsheet context menu in this case 1361 checkSpreadsheetMenu(); 1362 m_spreadsheetMenu->exec(global_pos); 1363 } 1364 1365 return true; 1366 } else if (event->type() == QEvent::KeyPress) { 1367 auto* key_event = static_cast<QKeyEvent*>(event); 1368 if (key_event->matches(QKeySequence::Copy)) 1369 copySelection(); 1370 else if (key_event->matches(QKeySequence::Paste)) { 1371 if (!m_readOnly) 1372 pasteIntoSelection(); 1373 } else if (key_event->key() == Qt::Key_Backspace || key_event->matches(QKeySequence::Delete)) 1374 clearSelectedCells(); 1375 else if (key_event->key() == Qt::Key_Return || key_event->key() == Qt::Key_Enter) { 1376 // only advance for return pressed events in the table, 1377 // ignore it in the search field that has its own handling for it 1378 if (watched == m_tableView) 1379 advanceCell(); 1380 } else if (key_event->key() == Qt::Key_Insert) { 1381 if (!m_editorEntered) { 1382 if (lastSelectedColumn(true) >= 0) 1383 insertColumnRight(); 1384 else 1385 insertRowBelow(); 1386 } 1387 } else if (key_event->key() == Qt::Key_Left) { 1388 // adjusted example from 1389 // https://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html 1390 // QModelIndex current = m_tableView->currentIndex(); 1391 // if (current.column() > 0) { 1392 // const QRect& rect = m_tableView->visualRect(current); 1393 // auto* scrollBar = m_tableView->horizontalScrollBar(); 1394 // if (rect.topLeft().x() < m_frozenTableView->columnWidth(0)) { 1395 // const int newValue = scrollBar->value() + rect.topLeft().x() - m_frozenTableView->columnWidth(0); 1396 // scrollBar->setValue(newValue); 1397 // } 1398 // } 1399 } else if (key_event->matches(QKeySequence::Find)) 1400 showSearchReplace(/* replace */ false); 1401 else if (key_event->matches(QKeySequence::Replace)) 1402 showSearchReplace(/* replace */ true); 1403 else if (key_event->key() == Qt::Key_Escape && m_searchReplaceWidget && m_searchReplaceWidget->isVisible()) 1404 m_searchReplaceWidget->hide(); 1405 else if (key_event->matches(QKeySequence::Cut)) 1406 cutSelection(); 1407 } 1408 1409 return QWidget::eventFilter(watched, event); 1410 } 1411 1412 /*! 1413 Advance current cell after [Return] or [Enter] was pressed 1414 */ 1415 void SpreadsheetView::advanceCell() { 1416 const QModelIndex& idx = m_tableView->currentIndex(); 1417 const int row = idx.row(); 1418 const int col = idx.column(); 1419 if (row + 1 == m_spreadsheet->rowCount()) 1420 m_spreadsheet->setRowCount(m_spreadsheet->rowCount() + 1); 1421 1422 m_tableView->setCurrentIndex(idx.sibling(row + 1, col)); 1423 } 1424 1425 /*! 1426 * disables cell data relevant actions in the spreadsheet menu if there're no cells available 1427 */ 1428 void SpreadsheetView::checkSpreadsheetMenu() { 1429 if (!m_plotDataMenu) 1430 initMenus(); 1431 1432 const auto& columns = m_spreadsheet->children<Column>(); 1433 1434 const bool cellsAvail = m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0; 1435 bool hasValues = this->hasValues(columns); 1436 m_plotDataMenu->setEnabled(hasValues); 1437 m_selectionMenu->setEnabled(hasValues); 1438 action_select_all->setEnabled(hasValues); 1439 action_clear_spreadsheet->setEnabled(hasValues); 1440 action_statistics_all_columns->setEnabled(hasValues); 1441 action_go_to_cell->setEnabled(cellsAvail); 1442 1443 // deactivate the "Clear masks" action if there are no masked cells 1444 bool hasMasked = false; 1445 for (auto* column : columns) { 1446 if (column->maskedIntervals().size() > 0) { 1447 hasMasked = true; 1448 break; 1449 } 1450 } 1451 1452 action_clear_masks->setVisible(hasMasked); 1453 1454 // deactivate the "Remove format" action if no columns are formatted 1455 bool hasFormat = false; 1456 for (auto* column : columns) { 1457 if (column->hasHeatmapFormat()) { 1458 hasFormat = true; 1459 break; 1460 } 1461 } 1462 1463 action_formatting_remove->setVisible(hasFormat); 1464 } 1465 1466 void SpreadsheetView::checkSpreadsheetSelectionMenu() { 1467 // deactivate mask/unmask actions for the selection 1468 // if there are no unmasked/masked cells in the current selection 1469 const QModelIndexList& indexes = m_tableView->selectionModel()->selectedIndexes(); 1470 bool hasMasked = false; 1471 bool hasUnmasked = false; 1472 for (auto& index : qAsConst(indexes)) { 1473 int row = index.row(); 1474 int col = index.column(); 1475 const auto* column = m_spreadsheet->column(col); 1476 // TODO: the null pointer check shouldn't be actually required here 1477 // but when deleting the columns the selection model in the view 1478 // and the aspect model sometimes get out of sync and we crash... 1479 if (!column) 1480 break; 1481 1482 if (!hasMasked && column->isMasked(row)) 1483 hasMasked = true; 1484 1485 if (!hasUnmasked && !column->isMasked(row)) 1486 hasUnmasked = true; 1487 1488 if (hasMasked && hasUnmasked) 1489 break; 1490 } 1491 1492 action_mask_selection->setEnabled(hasUnmasked); 1493 action_unmask_selection->setEnabled(hasMasked); 1494 } 1495 1496 void SpreadsheetView::checkColumnMenus(const QVector<Column*>& columns) { 1497 bool numeric = true; 1498 bool plottable = true; 1499 bool datetime = false; 1500 bool text = false; 1501 bool hasValues = false; 1502 bool hasFormat = false; 1503 bool hasEnoughValues = false; // enough for statistics (> 1) 1504 1505 for (const auto* col : columns) { 1506 if (!col->isNumeric()) { 1507 datetime = (col->columnMode() == AbstractColumn::ColumnMode::DateTime); 1508 if (!datetime) 1509 plottable = false; 1510 1511 numeric = false; 1512 break; 1513 } 1514 } 1515 1516 for (const auto* col : columns) { 1517 if (col->columnMode() == AbstractColumn::ColumnMode::Text) { 1518 text = true; 1519 break; 1520 } 1521 } 1522 1523 for (const auto* col : columns) { 1524 if (col->hasValues()) { 1525 hasValues = true; 1526 break; 1527 } 1528 } 1529 1530 for (const auto* col : columns) { 1531 if (col->availableRowCount() > 1) { 1532 hasEnoughValues = true; 1533 break; 1534 } 1535 } 1536 1537 for (const auto* col : columns) { 1538 if (col->hasHeatmapFormat()) { 1539 hasFormat = true; 1540 break; 1541 } 1542 } 1543 1544 if (isColumnSelected(0, true)) { 1545 action_freeze_columns->setVisible(true); 1546 if (m_frozenTableView) { 1547 if (!m_frozenTableView->isVisible()) { 1548 action_freeze_columns->setText(i18n("Freeze Column")); 1549 action_insert_column_left->setEnabled(true); 1550 action_insert_columns_left->setEnabled(true); 1551 } else { 1552 action_freeze_columns->setText(i18n("Unfreeze Column")); 1553 action_insert_column_left->setEnabled(false); 1554 action_insert_columns_left->setEnabled(false); 1555 } 1556 } 1557 } else 1558 action_freeze_columns->setVisible(false); 1559 1560 m_plotDataMenu->setEnabled(plottable && hasValues); 1561 m_analyzePlotMenu->setEnabled(numeric && hasValues); 1562 m_columnSetAsMenu->setEnabled(numeric); 1563 action_statistics_columns->setEnabled(hasEnoughValues); 1564 action_clear_columns->setEnabled(hasValues); 1565 m_formattingMenu->setEnabled(hasValues); 1566 action_formatting_remove->setVisible(hasFormat); 1567 1568 if (m_readOnly) 1569 return; 1570 1571 // generate data is only possible for numeric columns and if there are cells available 1572 const bool hasCells = m_spreadsheet->rowCount() > 0; 1573 m_columnGenerateDataMenu->setEnabled(hasCells); 1574 action_fill_row_numbers->setEnabled(numeric); 1575 action_fill_const->setEnabled(numeric); 1576 action_fill_equidistant->setEnabled(numeric || datetime); 1577 action_fill_random_nonuniform->setEnabled(numeric); 1578 action_fill_function->setEnabled(numeric); 1579 action_sample_values->setEnabled(hasValues); 1580 action_flatten_columns->setEnabled(hasValues); 1581 1582 // manipulate data is only possible for numeric and datetime and if there values. 1583 // datetime has only "add/subtract value", everything else is deactivated 1584 m_columnManipulateDataMenu->setEnabled((numeric || datetime || text) && hasValues); 1585 action_add_value->setEnabled(numeric || datetime); 1586 action_subtract_value->setEnabled(numeric || datetime); 1587 action_subtract_baseline->setEnabled(numeric); 1588 action_multiply_value->setEnabled(numeric); 1589 action_divide_value->setEnabled(numeric); 1590 action_reverse_columns->setEnabled(numeric); 1591 action_drop_values->setEnabled(numeric || text || datetime); 1592 action_mask_values->setEnabled(numeric || text || datetime); 1593 m_columnNormalizeMenu->setEnabled(numeric); 1594 m_columnLadderOfPowersMenu->setEnabled(numeric); 1595 } 1596 1597 bool SpreadsheetView::formulaModeActive() const { 1598 return m_model->formulaModeActive(); 1599 } 1600 1601 void SpreadsheetView::activateFormulaMode(bool on) { 1602 m_model->activateFormulaMode(on); 1603 } 1604 1605 void SpreadsheetView::goToNextColumn() { 1606 if (m_spreadsheet->columnCount() == 0) 1607 return; 1608 1609 QModelIndex idx = m_tableView->currentIndex(); 1610 int col = idx.column() + 1; 1611 if (col >= m_spreadsheet->columnCount()) 1612 col = 0; 1613 1614 m_tableView->setCurrentIndex(idx.sibling(idx.row(), col)); 1615 } 1616 1617 void SpreadsheetView::goToPreviousColumn() { 1618 if (m_spreadsheet->columnCount() == 0) 1619 return; 1620 1621 QModelIndex idx = m_tableView->currentIndex(); 1622 int col = idx.column() - 1; 1623 if (col < 0) 1624 col = m_spreadsheet->columnCount() - 1; 1625 1626 m_tableView->setCurrentIndex(idx.sibling(idx.row(), col)); 1627 } 1628 1629 void SpreadsheetView::cutSelection() { 1630 if (firstSelectedRow() < 0) 1631 return; 1632 1633 WAIT_CURSOR; 1634 m_spreadsheet->beginMacro(i18n("%1: cut selected cells", m_spreadsheet->name())); 1635 copySelection(); 1636 clearSelectedCells(); 1637 m_spreadsheet->endMacro(); 1638 RESET_CURSOR; 1639 } 1640 1641 void SpreadsheetView::copySelection() { 1642 PERFTRACE(QStringLiteral("copy selected cells")); 1643 const int first_col = firstSelectedColumn(); 1644 if (first_col == -1) 1645 return; 1646 const int last_col = lastSelectedColumn(); 1647 if (last_col == -2) 1648 return; 1649 const int first_row = firstSelectedRow(); 1650 if (first_row == -1) 1651 return; 1652 const int last_row = lastSelectedRow(); 1653 if (last_row == -2) 1654 return; 1655 const int cols = last_col - first_col + 1; 1656 const int rows = last_row - first_row + 1; 1657 1658 WAIT_CURSOR; 1659 QString output_str; 1660 1661 QVector<Column*> columns; 1662 QVector<char> formats; 1663 for (int c = 0; c < cols; c++) { 1664 Column* col = m_spreadsheet->column(first_col + c); 1665 columns << col; 1666 const auto* outFilter = static_cast<Double2StringFilter*>(col->outputFilter()); 1667 formats << outFilter->numericFormat(); 1668 } 1669 1670 const auto numberLocale = QLocale(); 1671 for (int r = 0; r < rows; r++) { 1672 for (int c = 0; c < cols; c++) { 1673 const Column* col_ptr = columns.at(c); 1674 if (isCellSelected(first_row + r, first_col + c)) { 1675 // if (formulaModeActive()) 1676 // output_str += col_ptr->formula(first_row + r); 1677 // else 1678 if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Double) 1679 output_str += numberLocale.toString(col_ptr->valueAt(first_row + r), formats.at(c), 16); // copy with max. precision 1680 else if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Integer || col_ptr->columnMode() == AbstractColumn::ColumnMode::BigInt) 1681 output_str += numberLocale.toString(col_ptr->valueAt(first_row + r)); 1682 else 1683 output_str += col_ptr->asStringColumn()->textAt(first_row + r); 1684 } 1685 if (c < cols - 1) 1686 output_str += QLatin1Char('\t'); 1687 } 1688 if (r < rows - 1) 1689 output_str += QLatin1Char('\n'); 1690 } 1691 1692 QApplication::clipboard()->setText(output_str); 1693 RESET_CURSOR; 1694 } 1695 /* 1696 bool determineLocale(const QString& value, QLocale& locale) { 1697 int pointIndex = value.indexOf(QLatin1Char('.')); 1698 int commaIndex = value.indexOf(QLatin1Char('.')); 1699 if (pointIndex != -1 && commaIndex != -1) { 1700 1701 } 1702 return false; 1703 }*/ 1704 1705 void SpreadsheetView::pasteIntoSelection() { 1706 if (m_spreadsheet->columnCount() < 1 || m_spreadsheet->rowCount() < 1) 1707 return; 1708 1709 const QMimeData* mime_data = QApplication::clipboard()->mimeData(); 1710 if (!mime_data->hasFormat(QStringLiteral("text/plain"))) 1711 return; 1712 1713 PERFTRACE(QStringLiteral("paste selected cells")); 1714 WAIT_CURSOR; 1715 m_spreadsheet->beginMacro(i18n("%1: paste from clipboard", m_spreadsheet->name())); 1716 1717 int first_col = firstSelectedColumn(); 1718 int last_col = lastSelectedColumn(); 1719 int first_row = firstSelectedRow(); 1720 int last_row = lastSelectedRow(); 1721 int input_row_count = 0; 1722 int input_col_count = 0; 1723 QVector<QStringList> cellTexts; 1724 1725 try { 1726 QString input_str = QString::fromUtf8(mime_data->data(QStringLiteral("text/plain"))).trimmed(); 1727 QString separator; 1728 if (input_str.indexOf(QLatin1String("\r\n")) != -1) 1729 separator = QLatin1String("\r\n"); 1730 else 1731 separator = QLatin1Char('\n'); 1732 1733 QStringList input_rows(input_str.split(separator)); 1734 input_str.clear(); // not needed anymore, release memory 1735 input_row_count = input_rows.count(); 1736 input_col_count = 0; 1737 bool hasTabs = false; 1738 if (input_row_count > 0 && input_rows.constFirst().indexOf(QLatin1Char('\t')) != -1) 1739 hasTabs = true; 1740 1741 const auto numberLocale = QLocale(); 1742 // TEST ' ' as group separator: 1743 // numberLocale = QLocale(QLocale::French, QLocale::France); 1744 const KConfigGroup group = Settings::group(QStringLiteral("Settings_General")); 1745 for (int i = 0; i < input_row_count; i++) { 1746 if (hasTabs) 1747 cellTexts.append(input_rows.at(i).split(QLatin1Char('\t'))); 1748 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 1749 else if (numberLocale.groupSeparator().trimmed().isEmpty() 1750 #else 1751 else if (numberLocale.groupSeparator().isSpace() 1752 #endif 1753 && !(numberLocale.numberOptions() & QLocale::OmitGroupSeparator)) // locale with ' ' as group separator && omit group separator not set 1754 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 1755 cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s\\s")), (Qt::SplitBehavior)0x1)); // split with two spaces 1756 #else 1757 cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s\\s")), (QString::SplitBehavior)0x1)); // split with two spaces 1758 #endif 1759 else 1760 cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s+")))); 1761 1762 if (cellTexts.at(i).count() > input_col_count) 1763 input_col_count = cellTexts.at(i).count(); 1764 } 1765 1766 input_rows.clear(); // not needed anymore, release memory 1767 1768 // when pasting DateTime data in the format 'yyyy-MM-dd hh:mm:ss' and similar, it get's split above because of the space separator. 1769 // here we check whether we have such a situation and merge the first two columns to get the proper value, 1770 // for example "2018-03-21 10:00:00" and not "2018-03-21" and "10:00:00" 1771 if (!cellTexts.isEmpty() && cellTexts.constFirst().size() > 1) { 1772 const auto& firstCell = cellTexts.constFirst().at(0); 1773 const auto& secondCell = cellTexts.constFirst().at(1); 1774 QString dateTimeFormat; // empty string, we'll auto-detect the format of the data 1775 const auto firstMode = AbstractFileFilter::columnMode(firstCell, dateTimeFormat, numberLocale); 1776 dateTimeFormat.clear(); 1777 const auto secondMode = AbstractFileFilter::columnMode(secondCell, dateTimeFormat, numberLocale); 1778 1779 // if both first columns are DateTime, check whether the combination of them is also DateTime 1780 if (firstMode == AbstractColumn::ColumnMode::DateTime && secondMode == AbstractColumn::ColumnMode::DateTime) { 1781 dateTimeFormat.clear(); 1782 const QString newCell = firstCell + QLatin1Char(' ') + secondCell; 1783 const auto newMode = AbstractFileFilter::columnMode(newCell, dateTimeFormat, numberLocale); 1784 if (newMode == AbstractColumn::ColumnMode::DateTime) { 1785 // merge the first two colums 1786 for (auto& row : cellTexts) { 1787 row[1] = row.at(0) + QLatin1Char(' ') + row.at(1); 1788 row.takeFirst(); 1789 } 1790 --input_col_count; 1791 } 1792 } 1793 } 1794 1795 // bool localeDetermined = false; 1796 1797 // expand the current selection to the needed size if 1798 // 1. there is no selection 1799 // 2. only one cell selected 1800 // 3. the whole column is selected (the use clicked on the header) 1801 // Also, set the proper column mode if the target column doesn't have any values yet 1802 // and set the proper column mode if the column is empty 1803 if ((first_col == -1 || first_row == -1) || (last_row == first_row && last_col == first_col) 1804 || (first_row == 0 && last_row == m_spreadsheet->rowCount() - 1)) { 1805 int current_row, current_col; 1806 getCurrentCell(¤t_row, ¤t_col); 1807 if (current_row == -1) 1808 current_row = 0; 1809 if (current_col == -1) 1810 current_col = 0; 1811 setCellSelected(current_row, current_col); 1812 first_col = current_col; 1813 first_row = current_row; 1814 last_row = first_row + input_row_count - 1; 1815 last_col = first_col + input_col_count - 1; 1816 const int columnCount = m_spreadsheet->columnCount(); 1817 // if the target columns that are already available don't have any values yet, 1818 // convert their mode to the mode of the data to be pasted 1819 for (int c = first_col; c <= last_col && c < columnCount; ++c) { 1820 Column* col = m_spreadsheet->column(c); 1821 if (col->hasValues()) 1822 continue; 1823 1824 // first non-empty value in the column to paste determines the column mode/type of the new column to be added 1825 const int curCol = c - first_col; 1826 QString nonEmptyValue; 1827 for (auto& r : cellTexts) { 1828 if (curCol < r.count() && !r.at(curCol).isEmpty()) { 1829 nonEmptyValue = r.at(curCol); 1830 break; 1831 } 1832 } 1833 1834 // if (!localeDetermined) 1835 // localeDetermined = determineLocale(nonEmptyValue, locale); 1836 1837 QString dateTimeFormat; // empty string, we'll auto-detect the format of the data 1838 const auto mode = AbstractFileFilter::columnMode(nonEmptyValue, dateTimeFormat, numberLocale); 1839 col->setColumnMode(mode); 1840 if (mode == AbstractColumn::ColumnMode::DateTime) { 1841 auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter()); 1842 filter->setFormat(dateTimeFormat); 1843 } 1844 } 1845 1846 // add columns if necessary 1847 if (last_col >= columnCount) { 1848 for (int c = 0; c < last_col - (columnCount - 1); ++c) { 1849 const int curCol = columnCount - first_col + c; 1850 // first non-empty value in the column to paste determines the column mode/type of the new column to be added 1851 QString nonEmptyValue; 1852 for (auto& r : cellTexts) { 1853 if (curCol < r.count() && !r.at(curCol).isEmpty()) { 1854 nonEmptyValue = r.at(curCol); 1855 break; 1856 } 1857 } 1858 1859 // if (!localeDetermined) 1860 // localeDetermined = determineLocale(nonEmptyValue, locale); 1861 1862 QString dateTimeFormat; // empty string, we'll auto-detect the format of the data 1863 const auto mode = AbstractFileFilter::columnMode(nonEmptyValue, dateTimeFormat, numberLocale); 1864 Column* new_col = new Column(QString::number(curCol), mode); 1865 if (mode == AbstractColumn::ColumnMode::DateTime) { 1866 auto* filter = static_cast<DateTime2StringFilter*>(new_col->outputFilter()); 1867 filter->setFormat(dateTimeFormat); 1868 } 1869 new_col->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 1870 new_col->insertRows(0, m_spreadsheet->rowCount()); 1871 m_spreadsheet->addChild(new_col); 1872 } 1873 } 1874 1875 // add rows if necessary 1876 if (last_row >= m_spreadsheet->rowCount()) 1877 m_spreadsheet->appendRows(last_row + 1 - m_spreadsheet->rowCount()); 1878 1879 // select the rectangle to be pasted in 1880 setCellsSelected(first_row, first_col, last_row, last_col); 1881 } 1882 1883 const int rows = last_row - first_row + 1; 1884 const int cols = last_col - first_col + 1; 1885 for (int c = 0; c < cols && c < input_col_count; c++) { 1886 Column* col = m_spreadsheet->column(first_col + c); 1887 col->setSuppressDataChangedSignal(true); 1888 if (col->columnMode() == AbstractColumn::ColumnMode::Double) { 1889 if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) { 1890 QVector<double> new_data(rows); 1891 for (int r = 0; r < rows; ++r) { 1892 if (c < cellTexts.at(r).count()) 1893 new_data[r] = numberLocale.toDouble(cellTexts.at(r).at(c)); 1894 } 1895 col->setValues(new_data); 1896 } else { 1897 for (int r = 0; r < rows && r < input_row_count; r++) { 1898 if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) { 1899 if (!cellTexts.at(r).at(c).isEmpty()) 1900 col->setValueAt(first_row + r, numberLocale.toDouble(cellTexts.at(r).at(c))); 1901 else 1902 col->setValueAt(first_row + r, std::numeric_limits<double>::quiet_NaN()); 1903 } 1904 } 1905 } 1906 } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { 1907 if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) { 1908 QVector<int> new_data(rows); 1909 for (int r = 0; r < rows; ++r) { 1910 if (c < cellTexts.at(r).count()) 1911 new_data[r] = numberLocale.toInt(cellTexts.at(r).at(c)); 1912 } 1913 col->setIntegers(new_data); 1914 } else { 1915 for (int r = 0; r < rows && r < input_row_count; r++) { 1916 if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) { 1917 if (!cellTexts.at(r).at(c).isEmpty()) 1918 col->setIntegerAt(first_row + r, numberLocale.toInt(cellTexts.at(r).at(c))); 1919 else 1920 col->setIntegerAt(first_row + r, 0); 1921 } 1922 } 1923 } 1924 } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { 1925 if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) { 1926 QVector<qint64> new_data(rows); 1927 for (int r = 0; r < rows; ++r) 1928 new_data[r] = numberLocale.toLongLong(cellTexts.at(r).at(c)); 1929 col->setBigInts(new_data); 1930 } else { 1931 for (int r = 0; r < rows && r < input_row_count; r++) { 1932 if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) { 1933 if (!cellTexts.at(r).at(c).isEmpty()) 1934 col->setBigIntAt(first_row + r, numberLocale.toLongLong(cellTexts.at(r).at(c))); 1935 else 1936 col->setBigIntAt(first_row + r, 0); 1937 } 1938 } 1939 } 1940 } else { 1941 for (int r = 0; r < rows && r < input_row_count; r++) { 1942 if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count())) { 1943 // if (formulaModeActive()) 1944 // col->setFormula(first_row + r, cellTexts.at(r).at(c)); 1945 // else 1946 col->asStringColumn()->setTextAt(first_row + r, cellTexts.at(r).at(c)); 1947 } 1948 } 1949 } 1950 1951 col->setSuppressDataChangedSignal(false); 1952 col->setChanged(); 1953 } // end of for-loop 1954 } catch (std::bad_alloc&) { 1955 cellTexts.clear(); 1956 m_spreadsheet->endMacro(); 1957 RESET_CURSOR; 1958 KMessageBox::error(this, i18n("Not enough memory to finalize this operation.")); 1959 } 1960 1961 m_spreadsheet->endMacro(); 1962 RESET_CURSOR; 1963 } 1964 1965 void SpreadsheetView::maskSelection() { 1966 int first = firstSelectedRow(); 1967 if (first < 0) 1968 return; 1969 int last = lastSelectedRow(); 1970 1971 WAIT_CURSOR; 1972 m_spreadsheet->beginMacro(i18n("%1: mask selected cells", m_spreadsheet->name())); 1973 1974 const auto& columns = selectedColumns(false); 1975 QVector<CartesianPlot*> plots; 1976 1977 // determine the dependent plots 1978 for (auto* column : columns) 1979 column->addUsedInPlots(plots); 1980 1981 // suppress retransform in the dependent plots 1982 for (auto* plot : plots) 1983 plot->setSuppressRetransform(true); 1984 1985 // mask the selected cells 1986 for (auto* column : columns) { 1987 int col = m_spreadsheet->indexOfChild<Column>(column); 1988 for (int row = first; row <= last; row++) 1989 if (isCellSelected(row, col)) 1990 column->setMasked(row); 1991 } 1992 1993 // retransform the dependent plots 1994 for (auto* plot : plots) { 1995 plot->setSuppressRetransform(false); 1996 // TODO: check which ranges must be updated 1997 plot->dataChanged(-1, -1); 1998 } 1999 2000 // some cells were masked, enable the "clear masks" action 2001 action_clear_masks->setEnabled(true); 2002 2003 m_spreadsheet->endMacro(); 2004 RESET_CURSOR; 2005 } 2006 2007 void SpreadsheetView::unmaskSelection() { 2008 int first = firstSelectedRow(); 2009 if (first < 0) 2010 return; 2011 int last = lastSelectedRow(); 2012 2013 WAIT_CURSOR; 2014 m_spreadsheet->beginMacro(i18n("%1: unmask selected cells", m_spreadsheet->name())); 2015 2016 const auto& columns = selectedColumns(false); 2017 QVector<CartesianPlot*> plots; 2018 2019 // determine the dependent plots 2020 for (auto* column : columns) 2021 column->addUsedInPlots(plots); 2022 2023 // suppress retransform in the dependent plots 2024 for (auto* plot : plots) 2025 plot->setSuppressRetransform(true); 2026 2027 // unmask the selected cells 2028 for (auto* column : columns) { 2029 int col = m_spreadsheet->indexOfChild<Column>(column); 2030 for (int row = first; row <= last; row++) 2031 if (isCellSelected(row, col)) 2032 column->setMasked(row, false); 2033 } 2034 2035 // retransform the dependent plots 2036 for (auto* plot : plots) { 2037 plot->setSuppressRetransform(false); 2038 // TODO: check which ranges must be updated 2039 plot->dataChanged(-1, -1); 2040 } 2041 2042 m_spreadsheet->endMacro(); 2043 RESET_CURSOR; 2044 } 2045 2046 void SpreadsheetView::plotData(QAction* action) { 2047 auto type = static_cast<PlotDataDialog::PlotType>(action->data().toInt()); 2048 auto* dlg = new PlotDataDialog(m_spreadsheet, type); 2049 2050 // use all spreadsheet columns if no columns are selected 2051 auto columns = selectedColumns(true); 2052 if (columns.isEmpty()) 2053 columns = m_spreadsheet->children<Column>(); 2054 2055 dlg->setSelectedColumns(columns); 2056 dlg->exec(); 2057 } 2058 2059 void SpreadsheetView::plotAnalysisData() { 2060 const auto* action = dynamic_cast<const QAction*>(QObject::sender()); 2061 auto* dlg = new PlotDataDialog(m_spreadsheet, PlotDataDialog::PlotType::XYCurve); 2062 2063 auto type = static_cast<XYAnalysisCurve::AnalysisAction>(action->data().toInt()); 2064 dlg->setAnalysisAction(type); 2065 2066 // use all spreadsheet columns if no columns are selected 2067 auto columns = selectedColumns(true); 2068 if (columns.isEmpty()) 2069 columns = m_spreadsheet->children<Column>(); 2070 dlg->setSelectedColumns(columns); 2071 2072 dlg->exec(); 2073 } 2074 2075 void SpreadsheetView::fillSelectedCellsWithRowNumbers() { 2076 const auto& columns = selectedColumns(false); 2077 if (columns.isEmpty()) 2078 return; 2079 2080 int first = firstSelectedRow(); 2081 if (first < 0) 2082 return; 2083 int last = lastSelectedRow(); 2084 2085 WAIT_CURSOR; 2086 m_spreadsheet->beginMacro(i18n("%1: fill cells with row numbers", m_spreadsheet->name())); 2087 for (auto* col_ptr : columns) { 2088 int col = m_spreadsheet->indexOfChild<Column>(col_ptr); 2089 col_ptr->setSuppressDataChangedSignal(true); 2090 switch (col_ptr->columnMode()) { 2091 case AbstractColumn::ColumnMode::Double: { 2092 QVector<double> results(last - first + 1); 2093 for (int row = first; row <= last; row++) 2094 if (isCellSelected(row, col)) 2095 results[row - first] = row + 1; 2096 else 2097 results[row - first] = col_ptr->valueAt(row); 2098 col_ptr->replaceValues(first, results); 2099 break; 2100 } 2101 case AbstractColumn::ColumnMode::Integer: { 2102 QVector<int> results(last - first + 1); 2103 for (int row = first; row <= last; row++) 2104 if (isCellSelected(row, col)) 2105 results[row - first] = row + 1; 2106 else 2107 results[row - first] = col_ptr->integerAt(row); 2108 col_ptr->replaceInteger(first, results); 2109 break; 2110 } 2111 case AbstractColumn::ColumnMode::BigInt: { 2112 QVector<qint64> results(last - first + 1); 2113 for (int row = first; row <= last; row++) 2114 if (isCellSelected(row, col)) 2115 results[row - first] = row + 1; 2116 else 2117 results[row - first] = col_ptr->bigIntAt(row); 2118 col_ptr->replaceBigInt(first, results); 2119 break; 2120 } 2121 case AbstractColumn::ColumnMode::Text: { 2122 QVector<QString> results; 2123 for (int row = first; row <= last; row++) 2124 if (isCellSelected(row, col)) 2125 results << QString::number(row + 1); 2126 else 2127 results << col_ptr->textAt(row); 2128 col_ptr->replaceTexts(first, results); 2129 break; 2130 } 2131 // TODO: handle other modes 2132 case AbstractColumn::ColumnMode::DateTime: 2133 case AbstractColumn::ColumnMode::Month: 2134 case AbstractColumn::ColumnMode::Day: 2135 break; 2136 } 2137 2138 col_ptr->setSuppressDataChangedSignal(false); 2139 col_ptr->setChanged(); 2140 } 2141 m_spreadsheet->endMacro(); 2142 RESET_CURSOR; 2143 } 2144 2145 void SpreadsheetView::fillWithRowNumbers() { 2146 const auto& columns = selectedColumns(); 2147 if (columns.isEmpty()) 2148 return; 2149 2150 WAIT_CURSOR; 2151 m_spreadsheet->beginMacro(i18np("%1: fill column with row numbers", "%1: fill columns with row numbers", m_spreadsheet->name(), columns.count())); 2152 2153 const int rows = m_spreadsheet->rowCount(); 2154 2155 QVector<int> int_data(rows); 2156 for (int i = 0; i < rows; ++i) 2157 int_data[i] = i + 1; 2158 2159 for (auto* col : columns) { 2160 switch (col->columnMode()) { 2161 case AbstractColumn::ColumnMode::Integer: 2162 col->replaceInteger(0, int_data); 2163 break; 2164 case AbstractColumn::ColumnMode::Double: 2165 case AbstractColumn::ColumnMode::BigInt: 2166 col->setColumnMode(AbstractColumn::ColumnMode::Integer); 2167 col->replaceInteger(0, int_data); 2168 break; 2169 case AbstractColumn::ColumnMode::Text: 2170 case AbstractColumn::ColumnMode::DateTime: 2171 case AbstractColumn::ColumnMode::Day: 2172 case AbstractColumn::ColumnMode::Month: 2173 break; 2174 } 2175 } 2176 2177 m_spreadsheet->endMacro(); 2178 RESET_CURSOR; 2179 } 2180 2181 // TODO: this function is not used currently. 2182 void SpreadsheetView::fillSelectedCellsWithRandomNumbers() { 2183 const auto& columns = selectedColumns(false); 2184 if (columns.isEmpty()) 2185 return; 2186 2187 int first = firstSelectedRow(); 2188 int last = lastSelectedRow(); 2189 if (first < 0) 2190 return; 2191 2192 WAIT_CURSOR; 2193 m_spreadsheet->beginMacro(i18n("%1: fill cells with random values", m_spreadsheet->name())); 2194 QRandomGenerator rng(QTime::currentTime().msec()); 2195 for (auto* col_ptr : columns) { 2196 int col = m_spreadsheet->indexOfChild<Column>(col_ptr); 2197 col_ptr->setSuppressDataChangedSignal(true); 2198 switch (col_ptr->columnMode()) { 2199 case AbstractColumn::ColumnMode::Double: { 2200 QVector<double> results(last - first + 1); 2201 for (int row = first; row <= last; row++) 2202 if (isCellSelected(row, col)) 2203 results[row - first] = rng.generateDouble(); 2204 else 2205 results[row - first] = col_ptr->valueAt(row); 2206 col_ptr->replaceValues(first, results); 2207 break; 2208 } 2209 case AbstractColumn::ColumnMode::Integer: { 2210 QVector<int> results(last - first + 1); 2211 for (int row = first; row <= last; row++) 2212 if (isCellSelected(row, col)) 2213 results[row - first] = QRandomGenerator::global()->generate(); 2214 else 2215 results[row - first] = col_ptr->integerAt(row); 2216 col_ptr->replaceInteger(first, results); 2217 break; 2218 } 2219 case AbstractColumn::ColumnMode::BigInt: { 2220 QVector<qint64> results(last - first + 1); 2221 for (int row = first; row <= last; row++) 2222 if (isCellSelected(row, col)) 2223 results[row - first] = QRandomGenerator::global()->generate64(); 2224 else 2225 results[row - first] = col_ptr->bigIntAt(row); 2226 col_ptr->replaceBigInt(first, results); 2227 break; 2228 } 2229 case AbstractColumn::ColumnMode::Text: { 2230 QVector<QString> results; 2231 for (int row = first; row <= last; row++) 2232 if (isCellSelected(row, col)) 2233 results[row - first] = QString::number(QRandomGenerator::global()->generateDouble()); 2234 else 2235 results << col_ptr->textAt(row); 2236 col_ptr->replaceTexts(first, results); 2237 break; 2238 } 2239 case AbstractColumn::ColumnMode::DateTime: 2240 case AbstractColumn::ColumnMode::Month: 2241 case AbstractColumn::ColumnMode::Day: { 2242 QVector<QDateTime> results; 2243 QDate earliestDate(1, 1, 1); 2244 QDate latestDate(2999, 12, 31); 2245 QTime midnight(0, 0, 0, 0); 2246 for (int row = first; row <= last; row++) 2247 if (isCellSelected(row, col)) 2248 results << QDateTime(earliestDate.addDays((QRandomGenerator::global()->generateDouble()) * ((double)earliestDate.daysTo(latestDate))), 2249 midnight.addMSecs((QRandomGenerator::global()->generateDouble()) * 1000 * 60 * 60 * 24)); 2250 else 2251 results << col_ptr->dateTimeAt(row); 2252 col_ptr->replaceDateTimes(first, results); 2253 break; 2254 } 2255 } 2256 2257 col_ptr->setSuppressDataChangedSignal(false); 2258 col_ptr->setChanged(); 2259 } 2260 m_spreadsheet->endMacro(); 2261 RESET_CURSOR; 2262 } 2263 2264 void SpreadsheetView::fillWithRandomValues() { 2265 const auto& columns = selectedColumns(); 2266 if (columns.isEmpty()) 2267 return; 2268 2269 auto* dlg = new RandomValuesDialog(m_spreadsheet); 2270 dlg->setColumns(columns); 2271 dlg->exec(); 2272 } 2273 2274 void SpreadsheetView::fillWithEquidistantValues() { 2275 const auto& columns = selectedColumns(); 2276 if (columns.isEmpty()) 2277 return; 2278 2279 auto* dlg = new EquidistantValuesDialog(m_spreadsheet); 2280 dlg->setColumns(columns); 2281 dlg->exec(); 2282 } 2283 2284 void SpreadsheetView::fillWithFunctionValues() { 2285 const auto& columns = selectedColumns(); 2286 if (columns.isEmpty()) 2287 return; 2288 2289 auto* dlg = new FunctionValuesDialog(m_spreadsheet); 2290 dlg->setColumns(columns); 2291 dlg->exec(); 2292 } 2293 2294 void SpreadsheetView::fillSelectedCellsWithConstValues() { 2295 const auto& columns = selectedColumns(false); 2296 if (columns.isEmpty()) 2297 return; 2298 2299 int first = firstSelectedRow(); 2300 int last = lastSelectedRow(); 2301 if (first < 0) 2302 return; 2303 2304 bool doubleOk = false; 2305 bool intOk = false; 2306 bool bigIntOk = false; 2307 bool stringOk = false; 2308 double doubleValue = 0; 2309 int intValue = 0; 2310 qint64 bigIntValue = 0; 2311 QString stringValue; 2312 2313 m_spreadsheet->beginMacro(i18n("%1: fill cells with const values", m_spreadsheet->name())); 2314 for (auto* col_ptr : columns) { 2315 int col = m_spreadsheet->indexOfChild<Column>(col_ptr); 2316 col_ptr->setSuppressDataChangedSignal(true); 2317 switch (col_ptr->columnMode()) { 2318 case AbstractColumn::ColumnMode::Double: 2319 if (!doubleOk) 2320 doubleValue = QInputDialog::getDouble(this, 2321 i18n("Fill the selection with constant value"), 2322 i18n("Value"), 2323 0, 2324 -std::numeric_limits<double>::max(), 2325 std::numeric_limits<double>::max(), 2326 6, 2327 &doubleOk); 2328 if (doubleOk) { 2329 WAIT_CURSOR; 2330 QVector<double> results(last - first + 1); 2331 for (int row = first; row <= last; row++) { 2332 if (isCellSelected(row, col)) 2333 results[row - first] = doubleValue; 2334 else 2335 results[row - first] = col_ptr->valueAt(row); 2336 } 2337 col_ptr->replaceValues(first, results); 2338 RESET_CURSOR; 2339 } 2340 break; 2341 case AbstractColumn::ColumnMode::Integer: 2342 if (!intOk) 2343 intValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &intOk); 2344 if (intOk) { 2345 WAIT_CURSOR; 2346 QVector<int> results(last - first + 1); 2347 for (int row = first; row <= last; row++) { 2348 if (isCellSelected(row, col)) 2349 results[row - first] = intValue; 2350 else 2351 results[row - first] = col_ptr->integerAt(row); 2352 } 2353 col_ptr->replaceInteger(first, results); 2354 RESET_CURSOR; 2355 } 2356 break; 2357 case AbstractColumn::ColumnMode::BigInt: 2358 // TODO: getBigInt() 2359 if (!bigIntOk) 2360 bigIntValue = 2361 QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &bigIntOk); 2362 if (bigIntOk) { 2363 WAIT_CURSOR; 2364 QVector<qint64> results(last - first + 1); 2365 for (int row = first; row <= last; row++) { 2366 if (isCellSelected(row, col)) 2367 results[row - first] = bigIntValue; 2368 else 2369 results[row - first] = col_ptr->bigIntAt(row); 2370 } 2371 col_ptr->replaceBigInt(first, results); 2372 RESET_CURSOR; 2373 } 2374 break; 2375 case AbstractColumn::ColumnMode::Text: 2376 if (!stringOk) 2377 stringValue = 2378 QInputDialog::getText(this, i18n("Fill the selection with constant value"), i18n("Value"), QLineEdit::Normal, QString(), &stringOk); 2379 if (stringOk && !stringValue.isEmpty()) { 2380 WAIT_CURSOR; 2381 QVector<QString> results; 2382 for (int row = first; row <= last; row++) { 2383 if (isCellSelected(row, col)) 2384 results << stringValue; 2385 else 2386 results << col_ptr->textAt(row); 2387 } 2388 col_ptr->replaceTexts(first, results); 2389 RESET_CURSOR; 2390 } 2391 break; 2392 // TODO: handle other modes 2393 case AbstractColumn::ColumnMode::DateTime: 2394 case AbstractColumn::ColumnMode::Month: 2395 case AbstractColumn::ColumnMode::Day: 2396 break; 2397 } 2398 2399 col_ptr->setSuppressDataChangedSignal(false); 2400 col_ptr->setChanged(); 2401 } 2402 m_spreadsheet->endMacro(); 2403 } 2404 2405 void SpreadsheetView::formatHeatmap() { 2406 auto columns = selectedColumns(); 2407 if (columns.isEmpty()) 2408 columns = m_spreadsheet->children<Column>(); 2409 2410 if (columns.isEmpty()) 2411 return; 2412 2413 auto* dlg = new FormattingHeatmapDialog(m_spreadsheet); 2414 dlg->setColumns(columns); 2415 if (dlg->exec() == QDialog::Accepted) { 2416 const auto& format = dlg->format(); 2417 int count = columns.count(); 2418 if (count > 1) 2419 m_spreadsheet->beginMacro(i18n("%1: set heatmap format", m_spreadsheet->name())); 2420 for (auto* col : columns) 2421 col->setHeatmapFormat(format); 2422 if (count > 1) 2423 m_spreadsheet->endMacro(); 2424 } 2425 2426 delete dlg; 2427 } 2428 2429 void SpreadsheetView::removeFormat() { 2430 auto columns = selectedColumns(); 2431 if (columns.isEmpty()) 2432 columns = m_spreadsheet->children<Column>(); 2433 2434 int count = columns.count(); 2435 if (count > 1) 2436 m_spreadsheet->beginMacro(i18n("%1: remove heatmap format", m_spreadsheet->name())); 2437 for (auto* col : columns) 2438 col->removeFormat(); 2439 if (count > 1) 2440 m_spreadsheet->endMacro(); 2441 } 2442 2443 /*! 2444 Insert an empty column left to the first selected column 2445 */ 2446 void SpreadsheetView::insertColumnLeft() { 2447 insertColumnsLeft(1); 2448 } 2449 2450 /*! 2451 Insert multiple empty columns left to the firt selected column 2452 */ 2453 void SpreadsheetView::insertColumnsLeft() { 2454 bool ok = false; 2455 int count = QInputDialog::getInt(nullptr, 2456 i18n("Insert empty columns"), 2457 i18n("Enter the number of columns to insert"), 2458 1 /*value*/, 2459 1 /*min*/, 2460 1000 /*max*/, 2461 1 /*step*/, 2462 &ok); 2463 if (!ok) 2464 return; 2465 2466 insertColumnsLeft(count); 2467 } 2468 2469 /*! 2470 * private helper function doing the actual insertion of columns to the left 2471 */ 2472 void SpreadsheetView::insertColumnsLeft(int count) { 2473 const int first = firstSelectedColumn(); 2474 m_spreadsheet->insertColumns(first, count); 2475 } 2476 2477 /*! 2478 Insert an empty column right to the last selected column 2479 */ 2480 void SpreadsheetView::insertColumnRight() { 2481 insertColumnsRight(1); 2482 } 2483 2484 /*! 2485 Insert multiple empty columns right to the last selected column 2486 */ 2487 void SpreadsheetView::insertColumnsRight() { 2488 bool ok = false; 2489 int count = QInputDialog::getInt(nullptr, 2490 i18n("Insert empty columns"), 2491 i18n("Enter the number of columns to insert"), 2492 1 /*value*/, 2493 1 /*min*/, 2494 1000 /*max*/, 2495 1 /*step*/, 2496 &ok); 2497 if (!ok) 2498 return; 2499 2500 insertColumnsRight(count); 2501 } 2502 2503 /*! 2504 * private helper function doing the actual insertion of columns to the right 2505 */ 2506 // TODO: check whether this function can be rewritten to use Spreadsheet::insertColumns() 2507 void SpreadsheetView::insertColumnsRight(int count) { 2508 WAIT_CURSOR; 2509 m_spreadsheet->beginMacro(i18np("%1: insert empty column", "%1: insert empty columns", m_spreadsheet->name(), count)); 2510 2511 const int last = lastSelectedColumn(); 2512 const int cols = m_spreadsheet->columnCount(); 2513 2514 if (last >= 0) { 2515 if (last < m_spreadsheet->columnCount() - 1) { 2516 // determine the column next to the last selected column 2517 Column* nextCol = m_spreadsheet->child<Column>(last + 1); 2518 2519 for (int i = 0; i < count; ++i) { 2520 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double); 2521 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 2522 newCol->insertRows(0, m_spreadsheet->rowCount()); 2523 2524 // insert the new column before the column next to the last selected column 2525 m_spreadsheet->insertChildBefore(newCol, nextCol); 2526 } 2527 } else { 2528 for (int i = 0; i < count; ++i) { 2529 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double); 2530 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 2531 newCol->insertRows(0, m_spreadsheet->rowCount()); 2532 2533 // last column selected, no next column available -> add/append a new column 2534 if (!m_spreadsheet->statisticsSpreadsheet()) 2535 m_spreadsheet->addChild(newCol); 2536 else 2537 m_spreadsheet->insertChildBefore(newCol, m_spreadsheet->statisticsSpreadsheet()); 2538 } 2539 } 2540 } else { 2541 if (m_spreadsheet->columnCount() > 0) { 2542 for (int i = 0; i < count; ++i) { 2543 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double); 2544 newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 2545 newCol->insertRows(0, m_spreadsheet->rowCount()); 2546 2547 // columns available but no columns selected -> append the new column at the very end 2548 if (!m_spreadsheet->statisticsSpreadsheet()) 2549 m_spreadsheet->addChild(newCol); 2550 else 2551 m_spreadsheet->insertChildBefore(newCol, m_spreadsheet->statisticsSpreadsheet()); 2552 } 2553 } else { 2554 // no columns available anymore -> resize the spreadsheet and the new column to the default size 2555 KConfigGroup group = Settings::group(QStringLiteral("Spreadsheet")); 2556 const int rows = group.readEntry(QLatin1String("RowCount"), 100); 2557 m_spreadsheet->setRowCount(rows); 2558 2559 for (int i = 0; i < count; ++i) { 2560 Column* newCol = new Column(QString::number(cols + i + 1), AbstractColumn::ColumnMode::Double); 2561 (i == 0) ? newCol->setPlotDesignation(AbstractColumn::PlotDesignation::X) : newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 2562 newCol->insertRows(0, rows); 2563 2564 // add/append a new column 2565 if (!m_spreadsheet->statisticsSpreadsheet()) 2566 m_spreadsheet->addChild(newCol); 2567 else 2568 m_spreadsheet->insertChildBefore(newCol, m_spreadsheet->statisticsSpreadsheet()); 2569 } 2570 } 2571 } 2572 2573 m_spreadsheet->endMacro(); 2574 RESET_CURSOR; 2575 } 2576 2577 void SpreadsheetView::removeSelectedColumns() { 2578 WAIT_CURSOR; 2579 m_spreadsheet->beginMacro(i18n("%1: remove selected columns", m_spreadsheet->name())); 2580 2581 for (auto* column : selectedColumns()) 2582 m_spreadsheet->removeChild(column); 2583 2584 m_spreadsheet->endMacro(); 2585 RESET_CURSOR; 2586 } 2587 2588 void SpreadsheetView::clearSelectedColumns() { 2589 m_spreadsheet->clear(selectedColumns()); 2590 } 2591 2592 void SpreadsheetView::toggleFreezeColumn() { 2593 if (!m_frozenTableView) { 2594 m_frozenTableView = new QTableView(this); 2595 m_frozenTableView->setModel(m_model); 2596 2597 auto* delegate = new SpreadsheetItemDelegate(this); 2598 connect(delegate, &SpreadsheetItemDelegate::returnPressed, this, &SpreadsheetView::advanceCell); 2599 connect(delegate, &SpreadsheetItemDelegate::editorEntered, this, [=]() { 2600 m_editorEntered = true; 2601 }); 2602 connect(delegate, &SpreadsheetItemDelegate::closeEditor, this, [=]() { 2603 m_editorEntered = false; 2604 }); 2605 2606 m_frozenTableView->setItemDelegate(delegate); 2607 m_frozenTableView->setSelectionMode(QAbstractItemView::ExtendedSelection); 2608 2609 m_frozenTableView->setFocusPolicy(Qt::NoFocus); 2610 m_frozenTableView->verticalHeader()->hide(); 2611 m_frozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); 2612 m_frozenTableView->horizontalHeader()->installEventFilter(this); 2613 m_tableView->viewport()->stackUnder(m_frozenTableView); 2614 2615 // m_frozenTableView->setStyleSheet("QTableView { border: none;" 2616 // "background-color: #8EDE21;" 2617 // "selection-background-color: #999}"); 2618 m_frozenTableView->setSelectionModel(m_tableView->selectionModel()); 2619 for (int col = 1; col < m_model->columnCount(); ++col) 2620 m_frozenTableView->setColumnHidden(col, true); 2621 2622 m_frozenTableView->setColumnWidth(0, m_tableView->columnWidth(0)); 2623 2624 m_frozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 2625 m_frozenTableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); 2626 m_frozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 2627 2628 updateFrozenTableGeometry(); 2629 2630 // synchronize the sroll bars across the main and the frozen views 2631 connect(m_frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged, m_tableView->verticalScrollBar(), &QAbstractSlider::setValue); 2632 connect(m_tableView->verticalScrollBar(), &QAbstractSlider::valueChanged, m_frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue); 2633 } 2634 2635 m_frozenTableView->setVisible(!m_frozenTableView->isVisible()); 2636 } 2637 2638 void SpreadsheetView::setSelectionAs() { 2639 const auto& columns = selectedColumns(); 2640 if (!columns.size()) 2641 return; 2642 2643 m_spreadsheet->beginMacro(i18n("%1: set plot designation", m_spreadsheet->name())); 2644 2645 auto* action = dynamic_cast<QAction*>(QObject::sender()); 2646 if (!action) 2647 return; 2648 2649 auto pd = (AbstractColumn::PlotDesignation)action->data().toInt(); 2650 for (auto* col : columns) 2651 col->setPlotDesignation(pd); 2652 2653 m_spreadsheet->endMacro(); 2654 } 2655 2656 /*! 2657 * add, subtract, multiply, divide 2658 */ 2659 void SpreadsheetView::modifyValues() { 2660 const auto& columns = selectedColumns(); 2661 if (columns.isEmpty()) 2662 return; 2663 2664 const QAction* action = dynamic_cast<const QAction*>(QObject::sender()); 2665 auto op = (AddSubtractValueDialog::Operation)action->data().toInt(); 2666 auto* dlg = new AddSubtractValueDialog(m_spreadsheet, columns, op); 2667 dlg->exec(); 2668 } 2669 2670 void SpreadsheetView::reverseColumns() { 2671 const auto& columns = selectedColumns(); 2672 if (columns.isEmpty()) 2673 return; 2674 2675 WAIT_CURSOR; 2676 m_spreadsheet->beginMacro(i18np("%1: reverse column", "%1: reverse columns", m_spreadsheet->name(), columns.size())); 2677 for (auto* col : columns) { 2678 if (col->columnMode() == AbstractColumn::ColumnMode::Double) { 2679 // determine the last row containing a valid value, 2680 // ignore all following empty rows when doing the reverse 2681 auto* data = static_cast<QVector<double>*>(col->data()); 2682 QVector<double> new_data(*data); 2683 auto itEnd = new_data.begin(); 2684 auto it = new_data.begin(); 2685 while (it != new_data.end()) { 2686 if (!std::isnan(*it)) 2687 itEnd = it; 2688 ++it; 2689 } 2690 ++itEnd; 2691 2692 std::reverse(new_data.begin(), itEnd); 2693 col->setValues(new_data); 2694 } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { 2695 auto* data = static_cast<QVector<int>*>(col->data()); 2696 QVector<int> new_data(*data); 2697 std::reverse(new_data.begin(), new_data.end()); 2698 col->setIntegers(new_data); 2699 } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { 2700 auto* data = static_cast<QVector<qint64>*>(col->data()); 2701 QVector<qint64> new_data(*data); 2702 std::reverse(new_data.begin(), new_data.end()); 2703 col->setBigInts(new_data); 2704 } 2705 } 2706 m_spreadsheet->endMacro(); 2707 RESET_CURSOR; 2708 } 2709 2710 void SpreadsheetView::dropColumnValues() { 2711 const auto& columns = selectedColumns(); 2712 if (columns.isEmpty()) 2713 return; 2714 2715 auto* dlg = new DropValuesDialog(m_spreadsheet); 2716 dlg->setColumns(columns); 2717 dlg->exec(); 2718 } 2719 2720 void SpreadsheetView::maskColumnValues() { 2721 const auto& columns = selectedColumns(); 2722 if (columns.isEmpty()) 2723 return; 2724 2725 auto* dlg = new DropValuesDialog(m_spreadsheet, true); 2726 dlg->setColumns(columns); 2727 dlg->exec(); 2728 } 2729 2730 void SpreadsheetView::sampleColumnValues() { 2731 const auto& columns = selectedColumns(); 2732 if (columns.isEmpty()) 2733 return; 2734 2735 auto* dlg = new SampleValuesDialog(m_spreadsheet, this); 2736 dlg->setColumns(columns); 2737 dlg->exec(); 2738 } 2739 2740 void SpreadsheetView::flattenColumns() { 2741 const auto& columns = selectedColumns(); 2742 if (columns.isEmpty()) 2743 return; 2744 2745 // ensure all selected columns have the same column mode 2746 auto mode = columns.at(0)->columnMode(); 2747 for (auto* col : columns) { 2748 if (col->columnMode() != mode) { 2749 KMessageBox::error(this, i18n("The selected columns have different data types and cannot be flattened because of this. ")); 2750 return; 2751 } 2752 } 2753 2754 auto* dlg = new FlattenColumnsDialog(m_spreadsheet, this); 2755 dlg->setColumns(columns); 2756 dlg->exec(); 2757 } 2758 2759 // void SpreadsheetView::joinColumns() { 2760 // //TODO 2761 // } 2762 2763 void SpreadsheetView::normalizeSelectedColumns(QAction* action) { 2764 auto columns = selectedColumns(); 2765 if (columns.isEmpty()) 2766 return; 2767 2768 auto method = static_cast<NormalizationMethod>(action->data().toInt()); 2769 2770 double rescaleIntervalMin = 0.0; 2771 double rescaleIntervalMax = 0.0; 2772 if (method == Rescale) { 2773 auto* dlg = new RescaleDialog(this); 2774 dlg->setColumns(columns); 2775 int rc = dlg->exec(); 2776 if (rc != QDialog::Accepted) 2777 return; 2778 2779 rescaleIntervalMin = dlg->min(); 2780 rescaleIntervalMax = dlg->max(); 2781 delete dlg; 2782 } 2783 2784 WAIT_CURSOR; 2785 QStringList messages; 2786 auto* message = "Normalization of the column <i>%1</i> was not possible because of %2."; 2787 m_spreadsheet->beginMacro(i18n("%1: normalize columns", m_spreadsheet->name())); 2788 2789 for (auto* col : columns) { 2790 if (col->columnMode() != AbstractColumn::ColumnMode::Double && col->columnMode() != AbstractColumn::ColumnMode::Integer 2791 && col->columnMode() != AbstractColumn::ColumnMode::BigInt) 2792 continue; 2793 2794 if (col->columnMode() == AbstractColumn::ColumnMode::Integer || col->columnMode() == AbstractColumn::ColumnMode::BigInt) 2795 col->setColumnMode(AbstractColumn::ColumnMode::Double); 2796 2797 auto* data = static_cast<QVector<double>*>(col->data()); 2798 QVector<double> new_data(col->rowCount()); 2799 2800 switch (method) { 2801 case DivideBySum: { 2802 double sum = std::accumulate(data->begin(), data->end(), 0); 2803 if (sum != 0.0) { 2804 for (int i = 0; i < col->rowCount(); ++i) 2805 new_data[i] = data->operator[](i) / sum; 2806 } else { 2807 messages << i18n(message, col->name(), i18n("Sum = 0")); 2808 continue; 2809 } 2810 break; 2811 } 2812 case DivideByMin: { 2813 double min = col->minimum(); 2814 if (min != 0.0) { 2815 for (int i = 0; i < col->rowCount(); ++i) 2816 new_data[i] = data->operator[](i) / min; 2817 } else { 2818 messages << i18n(message, col->name(), i18n("Min = 0")); 2819 continue; 2820 } 2821 break; 2822 } 2823 case DivideByMax: { 2824 double max = col->maximum(); 2825 if (max != 0.0) { 2826 for (int i = 0; i < col->rowCount(); ++i) 2827 new_data[i] = data->operator[](i) / max; 2828 } else { 2829 messages << i18n(message, col->name(), i18n("Max = 0")); 2830 continue; 2831 } 2832 break; 2833 } 2834 case DivideByCount: { 2835 int count = data->size(); 2836 if (count != 0.0) { 2837 for (int i = 0; i < col->rowCount(); ++i) 2838 new_data[i] = data->operator[](i) / count; 2839 } else { 2840 messages << i18n(message, col->name(), i18n("Count = 0")); 2841 continue; 2842 } 2843 break; 2844 } 2845 case DivideByMean: { 2846 double mean = col->statistics().arithmeticMean; 2847 if (mean != 0.0) { 2848 for (int i = 0; i < col->rowCount(); ++i) 2849 new_data[i] = data->operator[](i) / mean; 2850 } else { 2851 messages << i18n(message, col->name(), i18n("Mean = 0")); 2852 continue; 2853 } 2854 break; 2855 } 2856 case DivideByMedian: { 2857 double median = col->statistics().median; 2858 if (median != 0.0) { 2859 for (int i = 0; i < col->rowCount(); ++i) 2860 new_data[i] = data->operator[](i) / median; 2861 } else { 2862 messages << i18n(message, col->name(), i18n("Median = 0")); 2863 continue; 2864 } 2865 break; 2866 } 2867 case DivideByMode: { 2868 double mode = col->statistics().mode; 2869 if (mode != 0.0 && !std::isnan(mode)) { 2870 for (int i = 0; i < col->rowCount(); ++i) 2871 new_data[i] = data->operator[](i) / mode; 2872 } else { 2873 if (mode == 0.0) 2874 messages << i18n(message, col->name(), i18n("Mode = 0")); 2875 else 2876 messages << i18n(message, col->name(), i18n("'Mode not defined'")); 2877 continue; 2878 } 2879 break; 2880 } 2881 case DivideByRange: { 2882 double range = col->statistics().maximum - col->statistics().minimum; 2883 if (range != 0.0) { 2884 for (int i = 0; i < col->rowCount(); ++i) 2885 new_data[i] = data->operator[](i) / range; 2886 } else { 2887 messages << i18n(message, col->name(), i18n("Range = 0")); 2888 continue; 2889 } 2890 break; 2891 } 2892 case DivideBySD: { 2893 double std = col->statistics().standardDeviation; 2894 if (std != 0.0) { 2895 for (int i = 0; i < col->rowCount(); ++i) 2896 new_data[i] = data->operator[](i) / std; 2897 } else { 2898 messages << i18n(message, col->name(), i18n("SD = 0")); 2899 continue; 2900 } 2901 break; 2902 } 2903 case DivideByMAD: { 2904 double mad = col->statistics().medianDeviation; 2905 if (mad != 0.0) { 2906 for (int i = 0; i < col->rowCount(); ++i) 2907 new_data[i] = data->operator[](i) / mad; 2908 } else { 2909 messages << i18n(message, col->name(), i18n("MAD = 0")); 2910 continue; 2911 } 2912 break; 2913 } 2914 case DivideByIQR: { 2915 double iqr = col->statistics().iqr; 2916 if (iqr != 0.0) { 2917 for (int i = 0; i < col->rowCount(); ++i) 2918 new_data[i] = data->operator[](i) / iqr; 2919 } else { 2920 messages << i18n(message, col->name(), i18n("IQR = 0")); 2921 continue; 2922 } 2923 break; 2924 } 2925 case ZScoreSD: { 2926 double mean = col->statistics().arithmeticMean; 2927 double std = col->statistics().standardDeviation; 2928 if (std != 0.0) { 2929 for (int i = 0; i < col->rowCount(); ++i) 2930 new_data[i] = (data->operator[](i) - mean) / std; 2931 } else { 2932 messages << i18n(message, col->name(), i18n("SD = 0")); 2933 continue; 2934 } 2935 break; 2936 } 2937 case ZScoreMAD: { 2938 double median = col->statistics().median; 2939 double mad = col->statistics().medianDeviation; 2940 if (mad != 0.0) { 2941 for (int i = 0; i < col->rowCount(); ++i) 2942 new_data[i] = (data->operator[](i) - median) / mad; 2943 } else { 2944 messages << i18n(message, col->name(), i18n("MAD = 0")); 2945 continue; 2946 } 2947 break; 2948 } 2949 case ZScoreIQR: { 2950 double median = col->statistics().median; 2951 double iqr = col->statistics().thirdQuartile - col->statistics().firstQuartile; 2952 if (iqr != 0.0) { 2953 for (int i = 0; i < col->rowCount(); ++i) 2954 new_data[i] = (data->operator[](i) - median) / iqr; 2955 } else { 2956 messages << i18n(message, col->name(), i18n("IQR = 0")); 2957 continue; 2958 } 2959 break; 2960 } 2961 case Rescale: { 2962 double min = col->statistics().minimum; 2963 double max = col->statistics().maximum; 2964 if (max - min != 0.0) { 2965 for (int i = 0; i < col->rowCount(); ++i) 2966 new_data[i] = rescaleIntervalMin + (data->operator[](i) - min) / (max - min) * (rescaleIntervalMax - rescaleIntervalMin); 2967 } else { 2968 messages << i18n(message, col->name(), i18n("Max - Min = 0")); 2969 continue; 2970 } 2971 break; 2972 } 2973 } 2974 2975 col->setValues(new_data); 2976 } 2977 m_spreadsheet->endMacro(); 2978 RESET_CURSOR; 2979 2980 if (!messages.isEmpty()) { 2981 QString info; 2982 for (const QString& message : messages) { 2983 if (!info.isEmpty()) 2984 info += QLatin1String("<br><br>"); 2985 info += message; 2986 } 2987 QMessageBox::warning(this, i18n("Normalization not possible"), info); 2988 } 2989 } 2990 2991 void SpreadsheetView::powerTransformSelectedColumns(QAction* action) { 2992 auto columns = selectedColumns(); 2993 if (columns.isEmpty()) 2994 return; 2995 2996 auto power = static_cast<TukeyLadderPower>(action->data().toInt()); 2997 2998 WAIT_CURSOR; 2999 m_spreadsheet->beginMacro(i18n("%1: power transform columns", m_spreadsheet->name())); 3000 3001 for (auto* col : columns) { 3002 if (col->columnMode() != AbstractColumn::ColumnMode::Double && col->columnMode() != AbstractColumn::ColumnMode::Integer 3003 && col->columnMode() != AbstractColumn::ColumnMode::BigInt) 3004 continue; 3005 3006 if (col->columnMode() == AbstractColumn::ColumnMode::Integer || col->columnMode() == AbstractColumn::ColumnMode::BigInt) 3007 col->setColumnMode(AbstractColumn::ColumnMode::Double); 3008 3009 auto* data = static_cast<QVector<double>*>(col->data()); 3010 QVector<double> new_data(col->rowCount()); 3011 3012 switch (power) { 3013 case InverseSquared: { 3014 for (int i = 0; i < col->rowCount(); ++i) { 3015 double x = data->operator[](i); 3016 if (x != 0.0) 3017 new_data[i] = 1 / gsl_pow_2(x); 3018 else 3019 new_data[i] = NAN; 3020 } 3021 break; 3022 } 3023 case Inverse: { 3024 for (int i = 0; i < col->rowCount(); ++i) { 3025 double x = data->operator[](i); 3026 if (x != 0.0) 3027 new_data[i] = 1 / x; 3028 else 3029 new_data[i] = NAN; 3030 } 3031 break; 3032 } 3033 case InverseSquareRoot: { 3034 for (int i = 0; i < col->rowCount(); ++i) { 3035 double x = data->operator[](i); 3036 if (x >= 0.0) 3037 new_data[i] = 1 / std::sqrt(x); 3038 else 3039 new_data[i] = NAN; 3040 } 3041 break; 3042 } 3043 case Log: { 3044 for (int i = 0; i < col->rowCount(); ++i) { 3045 double x = data->operator[](i); 3046 if (x >= 0.0) 3047 new_data[i] = log10(x); 3048 else 3049 new_data[i] = NAN; 3050 } 3051 break; 3052 } 3053 case SquareRoot: { 3054 for (int i = 0; i < col->rowCount(); ++i) { 3055 double x = data->operator[](i); 3056 if (x >= 0.0) 3057 new_data[i] = std::sqrt(x); 3058 else 3059 new_data[i] = NAN; 3060 } 3061 break; 3062 } 3063 case Squared: { 3064 for (int i = 0; i < col->rowCount(); ++i) { 3065 double x = data->operator[](i); 3066 new_data[i] = gsl_pow_2(x); 3067 } 3068 break; 3069 } 3070 case Cube: { 3071 for (int i = 0; i < col->rowCount(); ++i) { 3072 double x = data->operator[](i); 3073 new_data[i] = gsl_pow_3(x); 3074 } 3075 break; 3076 } 3077 } 3078 3079 col->setValues(new_data); 3080 } 3081 3082 m_spreadsheet->endMacro(); 3083 RESET_CURSOR; 3084 } 3085 3086 // TODO: either make this complete (support all column modes and normalization methods) or remove this code completely. 3087 /* 3088 void SpreadsheetView::normalizeSelection() { 3089 WAIT_CURSOR; 3090 m_spreadsheet->beginMacro(i18n("%1: normalize selection", m_spreadsheet->name())); 3091 double max = 0.0; 3092 for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++) 3093 if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Double) 3094 for (int row = 0; row < m_spreadsheet->rowCount(); row++) { 3095 if (isCellSelected(row, col) && m_spreadsheet->column(col)->valueAt(row) > max) 3096 max = m_spreadsheet->column(col)->valueAt(row); 3097 } 3098 3099 if (max != 0.0) { // avoid division by zero 3100 //TODO setSuppressDataChangedSignal 3101 for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++) 3102 if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Double) 3103 for (int row = 0; row < m_spreadsheet->rowCount(); row++) { 3104 if (isCellSelected(row, col)) 3105 m_spreadsheet->column(col)->setValueAt(row, m_spreadsheet->column(col)->valueAt(row) / max); 3106 } 3107 } 3108 m_spreadsheet->endMacro(); 3109 RESET_CURSOR; 3110 } 3111 */ 3112 3113 void SpreadsheetView::showAllColumnsStatistics() { 3114 showColumnStatistics(true); 3115 } 3116 3117 void SpreadsheetView::showColumnStatistics(bool forAll) { 3118 QString dlgTitle(i18n("%1: column statistics", m_spreadsheet->name())); 3119 QVector<Column*> columns; 3120 3121 // Column statistics can be shown for: 3122 // * all columns in the spreadsheet - called via the action_statistics_all_columns or when one single cell is selected (=no selection) 3123 // * all selected columns in the speadsheet - called via the context menu of the header 3124 // * selected column cells - called via the context menu in the spreadsheet with multiple selected cells 3125 const auto& indexes = m_tableView->selectionModel()->selectedIndexes(); 3126 if (forAll || indexes.size() <= 1) { 3127 for (int col = 0; col < m_spreadsheet->columnCount(); ++col) { 3128 auto* column = m_spreadsheet->column(col); 3129 if (column->hasValues()) 3130 columns << column; 3131 } 3132 } else { 3133 columns = selectedColumns(true); 3134 3135 // if no columns are fully selected, copy the selected cells into new Columns which will be processes in the statistics dialog 3136 if (columns.isEmpty()) { 3137 const auto& children = m_spreadsheet->children<Column>(); 3138 Column* targetColumn{nullptr}; 3139 QMap<int, int> columnMappings; // key = child column index in the spreadsheet, value = column index in the vector of new colums 3140 QMap<int, int> rowMappings; // key = child column index in the spreadsheet, value = last row index 3141 for (const auto& index : indexes) { 3142 int col = index.column(); 3143 int row = index.row(); 3144 const auto* sourceColumn = children.at(col); 3145 3146 if (columnMappings.contains(col)) 3147 targetColumn = columns.at(columnMappings[col]); 3148 else { 3149 targetColumn = new Column(i18n("Selection in %1", sourceColumn->name()), sourceColumn->columnMode()); 3150 columns << targetColumn; 3151 columnMappings[col] = columns.count() - 1; 3152 rowMappings[col] = 0; 3153 } 3154 3155 int targetRow = rowMappings[col]; 3156 3157 switch (targetColumn->columnMode()) { 3158 case AbstractColumn::ColumnMode::Double: 3159 targetColumn->setValueAt(targetRow, sourceColumn->valueAt(row)); 3160 break; 3161 case AbstractColumn::ColumnMode::Integer: 3162 targetColumn->setIntegerAt(targetRow, sourceColumn->integerAt(row)); 3163 break; 3164 case AbstractColumn::ColumnMode::BigInt: 3165 targetColumn->setBigIntAt(targetRow, sourceColumn->bigIntAt(row)); 3166 break; 3167 case AbstractColumn::ColumnMode::Text: 3168 targetColumn->setTextAt(targetRow, sourceColumn->textAt(row)); 3169 break; 3170 case AbstractColumn::ColumnMode::DateTime: 3171 case AbstractColumn::ColumnMode::Month: 3172 case AbstractColumn::ColumnMode::Day: 3173 targetColumn->setDateTimeAt(targetRow, sourceColumn->dateTimeAt(row)); 3174 break; 3175 } 3176 3177 rowMappings[col] = targetRow + 1; 3178 } 3179 } 3180 } 3181 3182 auto* dlg = new StatisticsDialog(dlgTitle, columns); 3183 dlg->setModal(true); 3184 dlg->show(); 3185 QApplication::processEvents(QEventLoop::AllEvents, 0); 3186 QTimer::singleShot(0, this, [=]() { 3187 dlg->showStatistics(); 3188 }); 3189 } 3190 3191 void SpreadsheetView::showRowStatistics() { 3192 QString dlgTitle(i18n("%1: row statistics", m_spreadsheet->name())); 3193 3194 QVector<Column*> columns; 3195 const auto& rows = m_tableView->selectionModel()->selectedRows(); 3196 3197 for (int i = 0; i < rows.count(); ++i) { 3198 int row = rows.at(i).row(); 3199 QVector<double> rowValues; 3200 for (int j = 0; j < m_spreadsheet->columnCount(); ++j) 3201 rowValues << m_spreadsheet->column(j)->valueAt(row); 3202 columns << new Column(i18n("Row %1", row + 1), rowValues); 3203 } 3204 3205 auto* dlg = new StatisticsDialog(dlgTitle, columns); 3206 dlg->showStatistics(); 3207 3208 if (dlg->exec() == QDialog::Accepted) { 3209 qDeleteAll(columns); 3210 columns.clear(); 3211 } 3212 } 3213 3214 /*! 3215 Insert an empty row above(=before) the first selected row 3216 */ 3217 void SpreadsheetView::insertRowAbove() { 3218 insertRowsAbove(1); 3219 } 3220 3221 /*! 3222 Insert multiple empty rows above(=before) the first selected row 3223 */ 3224 void SpreadsheetView::insertRowsAbove() { 3225 bool ok = false; 3226 int count = QInputDialog::getInt(nullptr, 3227 i18n("Insert multiple rows"), 3228 i18n("Enter the number of rows to insert"), 3229 1 /*value*/, 3230 1 /*min*/, 3231 1000000 /*max*/, 3232 1 /*step*/, 3233 &ok); 3234 if (ok) 3235 insertRowsAbove(count); 3236 } 3237 3238 /*! 3239 * private helper function doing the actual insertion of rows above 3240 */ 3241 void SpreadsheetView::insertRowsAbove(int count) { 3242 int first = firstSelectedRow(); 3243 if (first < 0) 3244 return; 3245 3246 WAIT_CURSOR; 3247 m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count)); 3248 m_spreadsheet->insertRows(first, count); 3249 m_spreadsheet->endMacro(); 3250 RESET_CURSOR; 3251 } 3252 3253 /*! 3254 Insert an empty row below the last selected row 3255 */ 3256 void SpreadsheetView::insertRowBelow() { 3257 insertRowsBelow(1); 3258 } 3259 3260 /*! 3261 Insert an empty row below the last selected row 3262 */ 3263 void SpreadsheetView::insertRowsBelow() { 3264 bool ok = false; 3265 int count = QInputDialog::getInt(nullptr, 3266 i18n("Insert multiple rows"), 3267 i18n("Enter the number of rows to insert"), 3268 1 /*value*/, 3269 1 /*min*/, 3270 1000000 /*max*/, 3271 1 /*step*/, 3272 &ok); 3273 if (ok) 3274 insertRowsBelow(count); 3275 } 3276 3277 /*! 3278 * private helper function doing the actual insertion of rows below 3279 */ 3280 void SpreadsheetView::insertRowsBelow(int count) { 3281 int last = lastSelectedRow(); 3282 if (last < 0) 3283 return; 3284 3285 WAIT_CURSOR; 3286 m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count)); 3287 3288 if (last < m_spreadsheet->rowCount() - 1) 3289 m_spreadsheet->insertRows(last + 1, count); // insert before the next to the last selected row 3290 else 3291 m_spreadsheet->appendRows(count); // append new rows at the end 3292 3293 m_spreadsheet->endMacro(); 3294 RESET_CURSOR; 3295 } 3296 3297 void SpreadsheetView::removeSelectedRows() { 3298 if (firstSelectedRow() < 0) 3299 return; 3300 3301 WAIT_CURSOR; 3302 m_spreadsheet->beginMacro(i18n("%1: remove selected rows", m_spreadsheet->name())); 3303 // TODO setSuppressDataChangedSignal 3304 for (const auto& i : selectedRows().intervals()) 3305 m_spreadsheet->removeRows(i.start(), i.size()); 3306 m_spreadsheet->endMacro(); 3307 RESET_CURSOR; 3308 } 3309 3310 void SpreadsheetView::clearSelectedCells() { 3311 // don't try to clear values if the selected cells don't have any values at all 3312 bool empty = true; 3313 3314 const auto& columns = m_spreadsheet->children<Column>(); 3315 const auto& indexes = m_tableView->selectionModel()->selectedIndexes(); 3316 for (const auto& index : indexes) { 3317 if (columns.at(index.column())->isValid(index.row())) { 3318 empty = false; 3319 break; 3320 } 3321 if (!empty) 3322 break; 3323 } 3324 3325 if (empty) 3326 return; 3327 3328 WAIT_CURSOR; 3329 m_spreadsheet->beginMacro(i18n("%1: clear selected cells", m_spreadsheet->name())); 3330 for (auto* column : columns) { 3331 column->setSuppressDataChangedSignal(true); 3332 // if (formulaModeActive()) { 3333 // int col = m_spreadsheet->indexOfChild<Column>(column); 3334 // for (int row = last; row >= first; row--) 3335 // if (isCellSelected(row, col)) 3336 // column->setFormula(row, QString()); 3337 // } else { 3338 int index = m_spreadsheet->indexOfChild<Column>(column); 3339 if (isColumnSelected(index, true)) { 3340 // if the whole column is selected, clear directly instead of looping over the rows 3341 column->clear(); 3342 } else { 3343 for (const auto& index : indexes) 3344 columns.at(index.column())->asStringColumn()->setTextAt(index.row(), QString()); 3345 } 3346 3347 column->setSuppressDataChangedSignal(false); 3348 column->setChanged(); 3349 } 3350 m_spreadsheet->endMacro(); 3351 RESET_CURSOR; 3352 } 3353 3354 void SpreadsheetView::goToCell() { 3355 auto* dlg = new GoToDialog(this); 3356 if (dlg->exec() == QDialog::Accepted) { 3357 int row = dlg->row(); 3358 if (row < 1) 3359 row = 1; 3360 if (row > m_spreadsheet->rowCount()) 3361 row = m_spreadsheet->rowCount(); 3362 3363 int col = dlg->column(); 3364 if (col < 1) 3365 col = 1; 3366 if (col > m_spreadsheet->columnCount()) 3367 col = m_spreadsheet->columnCount(); 3368 3369 goToCell(row - 1, col - 1); 3370 } 3371 delete dlg; 3372 } 3373 3374 void SpreadsheetView::searchReplace() { 3375 const auto* action = dynamic_cast<const QAction*>(QObject::sender()); 3376 if (action == action_search_replace) 3377 showSearchReplace(true); // search&replace mode 3378 else 3379 showSearchReplace(false); // search mode 3380 } 3381 3382 void SpreadsheetView::showSearchReplace(bool replace) { 3383 if (!m_searchReplaceWidget) { 3384 m_searchReplaceWidget = new SearchReplaceWidget(m_spreadsheet, this); 3385 static_cast<QVBoxLayout*>(this->layout())->addWidget(m_searchReplaceWidget); 3386 } 3387 3388 m_searchReplaceWidget->setReplaceEnabled(replace); 3389 3390 const auto& indexes = m_tableView->selectionModel()->selectedIndexes(); 3391 if (!indexes.isEmpty()) { 3392 const auto& firstIndex = indexes.constFirst(); 3393 const auto* column = m_spreadsheet->column(firstIndex.column()); 3394 const int row = firstIndex.row(); 3395 m_searchReplaceWidget->setInitialPattern(column->columnMode(), column->asStringColumn()->textAt(row)); 3396 } 3397 3398 m_searchReplaceWidget->show(); 3399 3400 // interrupt the event loop to show/render the widget first and set the focus 3401 // after this otherwise the focus in the search field is not set 3402 QTimer::singleShot(0, this, [=]() { 3403 m_searchReplaceWidget->setFocus(); 3404 }); 3405 } 3406 3407 /*! 3408 * sorts the selected columns separately of each other in ascending order. 3409 */ 3410 void SpreadsheetView::sortAscending() { 3411 const auto& cols = selectedColumns(); 3412 if (cols.isEmpty() || !hasValues(cols)) 3413 return; 3414 3415 for (auto* col : cols) 3416 col->setSuppressDataChangedSignal(true); 3417 m_spreadsheet->sortColumns(nullptr, cols, true); 3418 for (auto* col : cols) { 3419 col->setSuppressDataChangedSignal(false); 3420 col->setChanged(); 3421 } 3422 } 3423 3424 /*! 3425 * sorts the selected columns separately of each other in descending order. 3426 */ 3427 void SpreadsheetView::sortDescending() { 3428 const auto& cols = selectedColumns(); 3429 if (cols.isEmpty() || !hasValues(cols)) 3430 return; 3431 3432 for (auto* col : cols) 3433 col->setSuppressDataChangedSignal(true); 3434 m_spreadsheet->sortColumns(nullptr, cols, false); 3435 for (auto* col : cols) { 3436 col->setSuppressDataChangedSignal(false); 3437 col->setChanged(); 3438 } 3439 } 3440 3441 /*! 3442 * custom sort of all or selected columns in the spreadsheet together by allowing 3443 * to specify the "sort by"-column(s) in the SortDialog. 3444 */ 3445 void SpreadsheetView::sortCustom() { 3446 const auto& cols = selectedColumns(); 3447 QVector<Column*> columnsToSort; 3448 Column* leadingColumn{nullptr}; 3449 3450 bool sortAll{true}; 3451 if (cols.size() == 0) 3452 // no columns selected, the function is being called from the spreadsheet context menu, 3453 // sort all columns 3454 columnsToSort = m_spreadsheet->children<Column>(); 3455 else if (cols.size() == 1) { 3456 // one single column selected, sort the whole spreadsheet (all columns) 3457 // with the current selected column being the leading column 3458 columnsToSort = m_spreadsheet->children<Column>(); 3459 leadingColumn = cols.constFirst(); 3460 } else { 3461 columnsToSort = cols; 3462 sortAll = (cols.size() == m_spreadsheet->columnCount()); 3463 } 3464 3465 // don't do any sort if there are no values yet in the columns 3466 if (!hasValues(columnsToSort)) 3467 return; 3468 3469 for (auto* col : columnsToSort) 3470 col->setSuppressDataChangedSignal(true); 3471 3472 auto* dlg = new SortDialog(this, sortAll); 3473 connect(dlg, &SortDialog::sort, m_spreadsheet, &Spreadsheet::sortColumns); 3474 dlg->setColumns(columnsToSort, leadingColumn); 3475 3476 int rc = dlg->exec(); 3477 3478 for (auto* col : columnsToSort) { 3479 col->setSuppressDataChangedSignal(false); 3480 if (rc == QDialog::Accepted) 3481 col->setChanged(); 3482 } 3483 } 3484 3485 bool SpreadsheetView::hasValues(const QVector<Column*> cols) { 3486 bool hasValues = false; 3487 for (const auto* col : cols) { 3488 if (col->hasValues()) { 3489 hasValues = true; 3490 break; 3491 } 3492 } 3493 3494 return hasValues; 3495 } 3496 3497 /*! 3498 Cause a repaint of the header. 3499 */ 3500 void SpreadsheetView::updateHeaderGeometry(Qt::Orientation o, int first, int last) { 3501 // TODO 3502 if (o != Qt::Horizontal || last < first) 3503 return; 3504 m_tableView->horizontalHeader()->setStretchLastSection(true); // ugly hack (flaw in Qt? Does anyone know a better way?) 3505 m_tableView->horizontalHeader()->updateGeometry(); 3506 m_tableView->horizontalHeader()->setStretchLastSection(false); // ugly hack part 2 3507 } 3508 3509 /*! 3510 selects the column \c column in the speadsheet view . 3511 */ 3512 void SpreadsheetView::selectColumn(int column) { 3513 const auto& index = m_model->index(0, column); 3514 m_tableView->scrollTo(index); 3515 QItemSelection selection(index, m_model->index(m_spreadsheet->rowCount() - 1, column)); 3516 m_suppressSelectionChangedEvent = true; 3517 m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); 3518 m_suppressSelectionChangedEvent = false; 3519 } 3520 3521 /*! 3522 deselects the column \c column in the speadsheet view . 3523 */ 3524 void SpreadsheetView::deselectColumn(int column) { 3525 QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount() - 1, column)); 3526 m_suppressSelectionChangedEvent = true; 3527 m_tableView->selectionModel()->select(selection, QItemSelectionModel::Deselect); 3528 m_suppressSelectionChangedEvent = false; 3529 } 3530 3531 /*! 3532 called when a column in the speadsheet view was clicked (click in the header). 3533 Propagates the selection of the column to the \c Spreadsheet object 3534 (a click in the header always selects the column). 3535 */ 3536 void SpreadsheetView::columnClicked(int column) { 3537 m_spreadsheet->setColumnSelectedInView(column, true); 3538 } 3539 3540 /*! 3541 called on selections changes. Propagates the selection/deselection of columns to the \c Spreadsheet object. 3542 */ 3543 void SpreadsheetView::selectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { 3544 if (m_suppressSelectionChangedEvent) 3545 return; 3546 3547 auto* selModel = m_tableView->selectionModel(); 3548 for (int i = 0; i < m_spreadsheet->columnCount(); i++) 3549 m_spreadsheet->setColumnSelectedInView(i, selModel->isColumnSelected(i)); 3550 3551 // determine the number of selected cells, columns, missing values and masked values in the current selection and show this information in the status bar. 3552 const auto& indexes = m_tableView->selectionModel()->selectedIndexes(); 3553 QString resultString = QString(); 3554 if (indexes.empty() || indexes.count() == 1) { 3555 Q_EMIT m_spreadsheet->statusInfo(resultString); 3556 return; 3557 } 3558 3559 QPair<int, int> selectedRowCol = qMakePair(selectedRowCount(false), selectedColumnCount(false)); 3560 const auto& columns = m_spreadsheet->children<Column>(); 3561 int maskedValuesCount = 0; 3562 int missingValuesCount = 0; 3563 int selectedCellsCount = 0; 3564 for (const auto& index : indexes) { 3565 const int col = index.column(); 3566 const int row = index.row(); 3567 selectedCellsCount++; 3568 auto& column = columns.at(col); 3569 if (!column->isValid(row)) 3570 missingValuesCount++; 3571 if (column->isMasked(row)) 3572 maskedValuesCount++; 3573 } 3574 3575 QString selectedRowsText = i18np("row", "rows", selectedRowCol.first); 3576 QString selectedColumnsText = i18np("column", "columns", selectedRowCol.second); 3577 QString selectedCellsText = i18n("cells"); 3578 QString maskedValuesText = (maskedValuesCount == 1) ? i18n("masked value") : (!maskedValuesCount) ? QString() : i18n("masked values"); 3579 QString missingValuesText = (missingValuesCount == 1) ? i18n("missing value") : (!missingValuesCount) ? QString() : i18n("missing values"); 3580 QString maskedValuesCountText = (!maskedValuesCount) ? QString() : i18n(" , ") + i18n("%1", maskedValuesCount); 3581 QString missingValuesCountText = (!missingValuesCount) ? QString() : i18n(", ") + i18n("%1", missingValuesCount); 3582 3583 if (selectedCellsCount == selectedRowCol.first * selectedRowCol.second) 3584 resultString = i18n("Selected: %1 %2 , %3 %4%5 %6 %7 %8", 3585 selectedRowCol.first, 3586 selectedRowsText, 3587 selectedRowCol.second, 3588 selectedColumnsText, 3589 maskedValuesCountText, 3590 maskedValuesText, 3591 missingValuesCountText, 3592 missingValuesText); 3593 3594 else 3595 resultString = i18n("Selected: %1 %2%3 %4 %5 %6", 3596 selectedCellsCount, 3597 selectedCellsText, 3598 maskedValuesCountText, 3599 maskedValuesText, 3600 missingValuesCountText, 3601 missingValuesText); 3602 3603 Q_EMIT m_spreadsheet->statusInfo(resultString); 3604 } 3605 3606 bool SpreadsheetView::exportView() { 3607 auto* dlg = new ExportSpreadsheetDialog(this); 3608 dlg->setProjectFileName(m_spreadsheet->project()->fileName()); 3609 dlg->setFileName(m_spreadsheet->name()); 3610 #ifdef HAVE_FITS 3611 dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table")); 3612 #endif 3613 for (int i = 0; i < m_spreadsheet->columnCount(); ++i) { 3614 if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::ColumnMode::Double) { 3615 dlg->setExportToImage(false); 3616 break; 3617 } 3618 } 3619 if (selectedColumnCount(false /* partial selection */) == 0) 3620 dlg->setExportSelection(false); 3621 3622 bool ret; 3623 if ((ret = dlg->exec()) == QDialog::Accepted) { 3624 const QString path = dlg->path(); 3625 const bool exportHeader = dlg->exportHeader(); 3626 WAIT_CURSOR; 3627 switch (dlg->format()) { 3628 case ExportSpreadsheetDialog::Format::ASCII: { 3629 const QString separator = dlg->separator(); 3630 const QLocale::Language format = dlg->numberFormat(); 3631 exportToFile(path, exportHeader, separator, format); 3632 break; 3633 } 3634 case ExportSpreadsheetDialog::Format::LaTeX: { 3635 const bool exportLatexHeader = dlg->exportLatexHeader(); 3636 const bool gridLines = dlg->gridLines(); 3637 const bool captions = dlg->captions(); 3638 const bool skipEmptyRows = dlg->skipEmptyRows(); 3639 const bool exportEntire = dlg->entireSpreadheet(); 3640 exportToLaTeX(path, exportHeader, gridLines, captions, exportLatexHeader, skipEmptyRows, exportEntire); 3641 break; 3642 } 3643 case ExportSpreadsheetDialog::Format::FITS: { 3644 #ifdef HAVE_FITS 3645 const int exportTo = dlg->exportToFits(); 3646 const bool commentsAsUnits = dlg->commentsAsUnitsFits(); 3647 exportToFits(path, exportTo, commentsAsUnits); 3648 #endif 3649 break; 3650 } 3651 case ExportSpreadsheetDialog::Format::XLSX: 3652 exportToXLSX(path, exportHeader); 3653 break; 3654 case ExportSpreadsheetDialog::Format::SQLite: 3655 exportToSQLite(path); 3656 break; 3657 } 3658 RESET_CURSOR; 3659 } 3660 delete dlg; 3661 3662 return ret; 3663 } 3664 3665 bool SpreadsheetView::printView() { 3666 QPrinter printer; 3667 auto* dlg = new QPrintDialog(&printer, this); 3668 dlg->setWindowTitle(i18nc("@title:window", "Print Spreadsheet")); 3669 3670 bool ret; 3671 if ((ret = dlg->exec()) == QDialog::Accepted) { 3672 print(&printer); 3673 } 3674 delete dlg; 3675 return ret; 3676 } 3677 3678 bool SpreadsheetView::printPreview() { 3679 auto* dlg = new QPrintPreviewDialog(this); 3680 connect(dlg, &QPrintPreviewDialog::paintRequested, this, &SpreadsheetView::print); 3681 return dlg->exec(); 3682 } 3683 3684 /*! 3685 prints the complete spreadsheet to \c printer. 3686 */ 3687 void SpreadsheetView::print(QPrinter* printer) const { 3688 WAIT_CURSOR; 3689 QPainter painter(printer); 3690 3691 const int dpiy = printer->logicalDpiY(); 3692 const int margin = (int)((1 / GSL_CONST_CGS_INCH) * dpiy); // 1 cm margins 3693 3694 QHeaderView* hHeader = m_tableView->horizontalHeader(); 3695 QHeaderView* vHeader = m_tableView->verticalHeader(); 3696 3697 const int rows = m_spreadsheet->rowCount(); 3698 const int cols = m_spreadsheet->columnCount(); 3699 int height = margin; 3700 const int vertHeaderWidth = vHeader->width(); 3701 const int width = printer->pageLayout().paintRectPixels(printer->resolution()).width() - 2 * margin; 3702 3703 int columnsPerTable = 0; 3704 int headerStringWidth = 0; 3705 int firstRowStringWidth = 0; 3706 bool tablesNeeded = false; 3707 for (int col = 0; col < cols; ++col) { 3708 headerStringWidth += m_tableView->columnWidth(col); 3709 firstRowStringWidth += m_spreadsheet->column(col)->asStringColumn()->textAt(0).length(); 3710 if ((headerStringWidth >= width) || (firstRowStringWidth >= width)) { 3711 tablesNeeded = true; 3712 break; 3713 } 3714 columnsPerTable++; 3715 } 3716 3717 int tablesCount = (columnsPerTable != 0) ? cols / columnsPerTable : 0; 3718 const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols; 3719 3720 if (!tablesNeeded) { 3721 tablesCount = 1; 3722 columnsPerTable = cols; 3723 } 3724 3725 if (remainingColumns > 0) 3726 tablesCount++; 3727 // Paint the horizontal header first 3728 for (int table = 0; table < tablesCount; ++table) { 3729 int right = margin + vertHeaderWidth; 3730 3731 painter.setFont(hHeader->font()); 3732 QString headerString = m_tableView->model()->headerData(0, Qt::Horizontal).toString(); 3733 QRect br; 3734 br = painter.boundingRect(br, Qt::AlignCenter, headerString); 3735 QRect tr(br); 3736 if (table != 0) 3737 height += tr.height(); 3738 painter.drawLine(right, height, right, height + br.height()); 3739 3740 int i = table * columnsPerTable; 3741 int toI = table * columnsPerTable + columnsPerTable; 3742 if ((remainingColumns > 0) && (table == tablesCount - 1)) { 3743 i = (tablesCount - 1) * columnsPerTable; 3744 toI = (tablesCount - 1) * columnsPerTable + remainingColumns; 3745 } 3746 3747 for (; i < toI; ++i) { 3748 headerString = m_tableView->model()->headerData(i, Qt::Horizontal).toString(); 3749 const int w = m_tableView->columnWidth(i); 3750 tr.setTopLeft(QPoint(right, height)); 3751 tr.setWidth(w); 3752 tr.setHeight(br.height()); 3753 3754 painter.drawText(tr, Qt::AlignCenter, headerString); 3755 right += w; 3756 painter.drawLine(right, height, right, height + tr.height()); 3757 } 3758 3759 painter.drawLine(margin + vertHeaderWidth, height, right - 1, height); // first horizontal line 3760 height += tr.height(); 3761 painter.drawLine(margin, height, right - 1, height); 3762 3763 // print table values 3764 for (i = 0; i < rows; ++i) { 3765 right = margin; 3766 QString cellText = m_tableView->model()->headerData(i, Qt::Vertical).toString() + QLatin1Char('\t'); 3767 tr = painter.boundingRect(tr, Qt::AlignCenter, cellText); 3768 painter.drawLine(right, height, right, height + tr.height()); 3769 3770 br.setTopLeft(QPoint(right, height)); 3771 br.setWidth(vertHeaderWidth); 3772 br.setHeight(tr.height()); 3773 painter.drawText(br, Qt::AlignCenter, cellText); 3774 right += vertHeaderWidth; 3775 painter.drawLine(right, height, right, height + tr.height()); 3776 int j = table * columnsPerTable; 3777 int toJ = table * columnsPerTable + columnsPerTable; 3778 if ((remainingColumns > 0) && (table == tablesCount - 1)) { 3779 j = (tablesCount - 1) * columnsPerTable; 3780 toJ = (tablesCount - 1) * columnsPerTable + remainingColumns; 3781 } 3782 for (; j < toJ; j++) { 3783 int w = m_tableView->columnWidth(j); 3784 cellText = m_spreadsheet->column(j)->isValid(i) ? m_spreadsheet->text(i, j) + QStringLiteral("\t") : QStringLiteral("- \t"); 3785 tr = painter.boundingRect(tr, Qt::AlignCenter, cellText); 3786 br.setTopLeft(QPoint(right, height)); 3787 br.setWidth(w); 3788 br.setHeight(tr.height()); 3789 painter.drawText(br, Qt::AlignCenter, cellText); 3790 right += w; 3791 painter.drawLine(right, height, right, height + tr.height()); 3792 } 3793 height += br.height(); 3794 painter.drawLine(margin, height, right - 1, height); 3795 3796 if (height >= printer->height() - margin) { 3797 printer->newPage(); 3798 height = margin; 3799 painter.drawLine(margin, height, right, height); 3800 } 3801 } 3802 } 3803 RESET_CURSOR; 3804 } 3805 3806 /*! 3807 * the spreadsheet can have empty rows at the end full with NaNs. 3808 * for the export we only need to export valid (non-empty) rows. 3809 * this functions determines the maximal row to export, or -1 3810 * if the spreadsheet doesn't have any data yet. 3811 */ 3812 int SpreadsheetView::maxRowToExport() const { 3813 int maxRow = -1; 3814 for (int j = 0; j < m_spreadsheet->columnCount(); ++j) { 3815 Column* col = m_spreadsheet->column(j); 3816 auto mode = col->columnMode(); 3817 if (mode == AbstractColumn::ColumnMode::Double) { 3818 for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { 3819 if (!std::isnan(col->valueAt(i)) && i > maxRow) 3820 maxRow = i; 3821 } 3822 } 3823 if (mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt) { 3824 // TODO: 3825 // integer column found. Since empty integer cells are equal to 0 3826 // at the moment, we need to export the whole column. 3827 // this logic needs to be adjusted once we're able to descriminate 3828 // between empty and 0 values for integer columns 3829 maxRow = m_spreadsheet->rowCount() - 1; 3830 break; 3831 } else if (mode == AbstractColumn::ColumnMode::DateTime) { 3832 for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { 3833 if (col->dateTimeAt(i).isValid() && i > maxRow) 3834 maxRow = i; 3835 } 3836 } else if (mode == AbstractColumn::ColumnMode::Text) { 3837 for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { 3838 if (!col->textAt(i).isEmpty() && i > maxRow) 3839 maxRow = i; 3840 } 3841 } 3842 } 3843 3844 return maxRow; 3845 } 3846 3847 void SpreadsheetView::exportToFile(const QString& path, const bool exportHeader, const QString& separator, QLocale::Language language) const { 3848 QFile file(path); 3849 if (!file.open(QFile::WriteOnly | QFile::Truncate)) { 3850 RESET_CURSOR; 3851 QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path)); 3852 return; 3853 } 3854 3855 PERFTRACE(QStringLiteral("export spreadsheet to file")); 3856 QTextStream out(&file); 3857 3858 int maxRow = maxRowToExport(); 3859 if (maxRow < 0) 3860 return; 3861 3862 const int cols = m_spreadsheet->columnCount(); 3863 QString sep = separator; 3864 sep = sep.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive); 3865 sep = sep.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); 3866 3867 // export header (column names) 3868 if (exportHeader) { 3869 for (int j = 0; j < cols; ++j) { 3870 out << '"' << m_spreadsheet->column(j)->name() << '"'; 3871 if (j != cols - 1) 3872 out << sep; 3873 } 3874 out << '\n'; 3875 } 3876 3877 // export values 3878 QLocale locale(language); 3879 for (int i = 0; i <= maxRow; ++i) { 3880 for (int j = 0; j < cols; ++j) { 3881 Column* col = m_spreadsheet->column(j); 3882 if (col->columnMode() == AbstractColumn::ColumnMode::Double) { 3883 const Double2StringFilter* out_fltr = static_cast<Double2StringFilter*>(col->outputFilter()); 3884 out << locale.toString(col->valueAt(i), out_fltr->numericFormat(), 16); // export with max. precision 3885 } else 3886 out << col->asStringColumn()->textAt(i); 3887 3888 if (j != cols - 1) 3889 out << sep; 3890 } 3891 out << '\n'; 3892 } 3893 } 3894 3895 void SpreadsheetView::exportToLaTeX(const QString& path, 3896 const bool exportHeaders, 3897 const bool gridLines, 3898 const bool captions, 3899 const bool latexHeaders, 3900 const bool skipEmptyRows, 3901 const bool exportEntire) const { 3902 QFile file(path); 3903 if (!file.open(QFile::WriteOnly | QFile::Truncate)) { 3904 RESET_CURSOR; 3905 QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path)); 3906 return; 3907 } 3908 3909 QList<Column*> toExport; 3910 int cols; 3911 int totalRowCount = 0; 3912 if (exportEntire) { 3913 cols = const_cast<SpreadsheetView*>(this)->m_spreadsheet->columnCount(); 3914 totalRowCount = m_spreadsheet->rowCount(); 3915 for (int col = 0; col < cols; ++col) 3916 toExport << m_spreadsheet->column(col); 3917 } else { 3918 cols = const_cast<SpreadsheetView*>(this)->selectedColumnCount(false /* partial selection */); 3919 const int firtsSelectedCol = const_cast<SpreadsheetView*>(this)->firstSelectedColumn(); 3920 bool rowsCalculated = false; 3921 for (int col = firtsSelectedCol; col < firtsSelectedCol + cols; ++col) { 3922 QVector<QString> textData; 3923 for (int row = 0; row < m_spreadsheet->rowCount(); ++row) { 3924 if (const_cast<SpreadsheetView*>(this)->isRowSelected(row)) { 3925 textData << m_spreadsheet->column(col)->asStringColumn()->textAt(row); 3926 if (!rowsCalculated) 3927 totalRowCount++; 3928 } 3929 } 3930 if (!rowsCalculated) 3931 rowsCalculated = true; 3932 Column* column = new Column(m_spreadsheet->column(col)->name(), textData); 3933 toExport << column; 3934 } 3935 } 3936 int columnsStringSize = 0; 3937 int columnsPerTable = 0; 3938 3939 for (int i = 0; i < cols; ++i) { 3940 int maxSize = -1; 3941 for (int j = 0; j < toExport.at(i)->asStringColumn()->rowCount(); ++j) { 3942 if (toExport.at(i)->asStringColumn()->textAt(j).size() > maxSize) 3943 maxSize = toExport.at(i)->asStringColumn()->textAt(j).size(); 3944 } 3945 columnsStringSize += maxSize; 3946 if (!toExport.at(i)->isValid(0)) 3947 columnsStringSize += 3; 3948 if (columnsStringSize > 65) 3949 break; 3950 ++columnsPerTable; 3951 } 3952 3953 const int tablesCount = (columnsPerTable != 0) ? cols / columnsPerTable : 0; 3954 const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols; 3955 3956 bool columnsSeparating = (cols > columnsPerTable); 3957 QTextStream out(&file); 3958 3959 const QString latexFullPath = QStandardPaths::findExecutable(QLatin1String("latex")); 3960 if (latexFullPath.isEmpty()) { 3961 DEBUG(Q_FUNC_INFO << ", WARNING: latex not found!") 3962 return; 3963 } 3964 3965 QProcess tex; 3966 tex.start(latexFullPath, QStringList() << QStringLiteral("--version"), QProcess::ReadOnly); 3967 tex.waitForFinished(500); 3968 QString texVersionOutput = QLatin1String(tex.readAllStandardOutput()); 3969 texVersionOutput = texVersionOutput.split(QLatin1Char('\n'))[0]; 3970 3971 int yearidx = -1; 3972 for (int i = texVersionOutput.size() - 1; i >= 0; --i) { 3973 if (texVersionOutput.at(i) == QLatin1Char('2')) { 3974 yearidx = i; 3975 break; 3976 } 3977 } 3978 3979 if (texVersionOutput.at(yearidx + 1) == QLatin1Char('/')) 3980 yearidx -= 3; 3981 3982 bool ok; 3983 texVersionOutput.mid(yearidx, 4).toInt(&ok); 3984 int version = -1; 3985 if (ok) 3986 version = texVersionOutput.mid(yearidx, 4).toInt(&ok); 3987 3988 if (latexHeaders) { 3989 out << QLatin1String("\\documentclass[11pt,a4paper]{article} \n"); 3990 out << QLatin1String("\\usepackage{geometry} \n"); 3991 out << QLatin1String("\\usepackage{xcolor,colortbl} \n"); 3992 if (version >= 2015) 3993 out << QLatin1String("\\extrafloats{1280} \n"); 3994 out << QLatin1String("\\definecolor{HeaderBgColor}{rgb}{0.81,0.81,0.81} \n"); 3995 out << QLatin1String("\\geometry{ \n"); 3996 out << QLatin1String("a4paper, \n"); 3997 out << QLatin1String("total={170mm,257mm}, \n"); 3998 out << QLatin1String("left=10mm, \n"); 3999 out << QLatin1String("top=10mm } \n"); 4000 4001 out << QLatin1String("\\begin{document} \n"); 4002 out << QLatin1String("\\title{LabPlot Spreadsheet Export to \\LaTeX{} } \n"); 4003 out << QLatin1String("\\author{LabPlot} \n"); 4004 out << QLatin1String("\\date{\\today} \n"); 4005 } 4006 4007 QString endTabularTable(QStringLiteral("\\end{tabular} \n \\end{table} \n")); 4008 QString tableCaption(QStringLiteral("\\caption{") + m_spreadsheet->name() + QStringLiteral("} \n")); 4009 QString beginTable(QStringLiteral("\\begin{table}[ht] \n")); 4010 4011 int rowCount = 0; 4012 const int maxRows = 45; 4013 bool captionRemoved = false; 4014 if (columnsSeparating) { 4015 QVector<int> emptyRowIndices; 4016 for (int table = 0; table < tablesCount; ++table) { 4017 QStringList textable; 4018 captionRemoved = false; 4019 textable << beginTable; 4020 4021 if (captions) 4022 textable << tableCaption; 4023 textable << QLatin1String("\\centering \n"); 4024 textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); 4025 for (int i = 0; i < columnsPerTable; ++i) 4026 textable << (gridLines ? QLatin1String(" c |") : QLatin1String(" c ")); 4027 textable << QLatin1String("} \n"); 4028 if (gridLines) 4029 textable << QLatin1String("\\hline \n"); 4030 4031 if (exportHeaders) { 4032 if (latexHeaders) 4033 textable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); 4034 for (int col = table * columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) { 4035 textable << toExport.at(col)->name(); 4036 if (col != ((table * columnsPerTable) + columnsPerTable) - 1) 4037 textable << QLatin1String(" & "); 4038 } 4039 textable << QLatin1String("\\\\ \n"); 4040 if (gridLines) 4041 textable << QLatin1String("\\hline \n"); 4042 } 4043 for (const auto& s : textable) 4044 out << s; 4045 4046 QStringList values; 4047 for (int row = 0; row < totalRowCount; ++row) { 4048 values.clear(); 4049 bool notEmpty = false; 4050 for (int col = table * columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) { 4051 if (toExport.at(col)->isValid(row)) { 4052 notEmpty = true; 4053 values << toExport.at(col)->asStringColumn()->textAt(row); 4054 } else 4055 values << QLatin1String("-"); 4056 if (col != ((table * columnsPerTable) + columnsPerTable) - 1) 4057 values << QLatin1String(" & "); 4058 } 4059 if (!notEmpty && skipEmptyRows) { 4060 if (!emptyRowIndices.contains(row)) 4061 emptyRowIndices << row; 4062 } 4063 if (emptyRowIndices.contains(row) && notEmpty) 4064 emptyRowIndices.remove(emptyRowIndices.indexOf(row)); 4065 4066 if (notEmpty || !skipEmptyRows) { 4067 for (const auto& s : values) 4068 out << s; 4069 out << QLatin1String("\\\\ \n"); 4070 if (gridLines) 4071 out << QLatin1String("\\hline \n"); 4072 rowCount++; 4073 if (rowCount == maxRows) { 4074 out << endTabularTable; 4075 out << QLatin1String("\\clearpage \n"); 4076 4077 if (captions) 4078 if (!captionRemoved) 4079 textable.removeAt(1); 4080 for (const auto& s : textable) 4081 out << s; 4082 rowCount = 0; 4083 if (!captionRemoved) 4084 captionRemoved = true; 4085 } 4086 } 4087 } 4088 out << endTabularTable; 4089 } 4090 4091 // new table for the remaining columns 4092 QStringList remainingTable; 4093 remainingTable << beginTable; 4094 if (captions) 4095 remainingTable << tableCaption; 4096 remainingTable << QLatin1String("\\centering \n"); 4097 remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); 4098 for (int c = 0; c < remainingColumns; ++c) 4099 remainingTable << (gridLines ? QLatin1String(" c |") : QLatin1String(" c ")); 4100 remainingTable << QLatin1String("} \n"); 4101 if (gridLines) 4102 remainingTable << QLatin1String("\\hline \n"); 4103 if (exportHeaders) { 4104 if (latexHeaders) 4105 remainingTable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); 4106 for (int col = 0; col < remainingColumns; ++col) { 4107 remainingTable << toExport.at(col + (tablesCount * columnsPerTable))->name(); 4108 if (col != remainingColumns - 1) 4109 remainingTable << QLatin1String(" & "); 4110 } 4111 remainingTable << QLatin1String("\\\\ \n"); 4112 if (gridLines) 4113 remainingTable << QLatin1String("\\hline \n"); 4114 } 4115 4116 for (const auto& s : remainingTable) 4117 out << s; 4118 4119 QStringList values; 4120 captionRemoved = false; 4121 for (int row = 0; row < totalRowCount; ++row) { 4122 values.clear(); 4123 bool notEmpty = false; 4124 for (int col = 0; col < remainingColumns; ++col) { 4125 if (toExport.at(col + (tablesCount * columnsPerTable))->isValid(row)) { 4126 notEmpty = true; 4127 values << toExport.at(col + (tablesCount * columnsPerTable))->asStringColumn()->textAt(row); 4128 } else 4129 values << QLatin1String("-"); 4130 if (col != remainingColumns - 1) 4131 values << QLatin1String(" & "); 4132 } 4133 if (!emptyRowIndices.contains(row) && !notEmpty) 4134 notEmpty = true; 4135 if (notEmpty || !skipEmptyRows) { 4136 for (const auto& s : values) 4137 out << s; 4138 out << QLatin1String("\\\\ \n"); 4139 if (gridLines) 4140 out << QLatin1String("\\hline \n"); 4141 rowCount++; 4142 if (rowCount == maxRows) { 4143 out << endTabularTable; 4144 out << QLatin1String("\\pagebreak[4] \n"); 4145 if (captions) 4146 if (!captionRemoved) 4147 remainingTable.removeAt(1); 4148 for (const auto& s : remainingTable) 4149 out << s; 4150 rowCount = 0; 4151 if (!captionRemoved) 4152 captionRemoved = true; 4153 } 4154 } 4155 } 4156 out << endTabularTable; 4157 } else { 4158 QStringList textable; 4159 textable << beginTable; 4160 if (captions) 4161 textable << tableCaption; 4162 textable << QLatin1String("\\centering \n"); 4163 textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); 4164 for (int c = 0; c < cols; ++c) 4165 textable << (gridLines ? QLatin1String(" c |") : QLatin1String(" c ")); 4166 textable << QLatin1String("} \n"); 4167 if (gridLines) 4168 textable << QLatin1String("\\hline \n"); 4169 if (exportHeaders) { 4170 if (latexHeaders) 4171 textable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); 4172 for (int col = 0; col < cols; ++col) { 4173 textable << toExport.at(col)->name(); 4174 if (col != cols - 1) 4175 textable << QLatin1String(" & "); 4176 } 4177 textable << QLatin1String("\\\\ \n"); 4178 if (gridLines) 4179 textable << QLatin1String("\\hline \n"); 4180 } 4181 4182 for (const auto& s : textable) 4183 out << s; 4184 QStringList values; 4185 captionRemoved = false; 4186 for (int row = 0; row < totalRowCount; ++row) { 4187 values.clear(); 4188 bool notEmpty = false; 4189 4190 for (int col = 0; col < cols; ++col) { 4191 if (toExport.at(col)->isValid(row)) { 4192 notEmpty = true; 4193 values << toExport.at(col)->asStringColumn()->textAt(row); 4194 } else 4195 values << QStringLiteral("-"); 4196 if (col != cols - 1) 4197 values << QStringLiteral(" & "); 4198 } 4199 4200 if (notEmpty || !skipEmptyRows) { 4201 for (const auto& s : values) 4202 out << s; 4203 out << QLatin1String("\\\\ \n"); 4204 if (gridLines) 4205 out << QLatin1String("\\hline \n"); 4206 rowCount++; 4207 if (rowCount == maxRows) { 4208 out << endTabularTable; 4209 out << QLatin1String("\\clearpage \n"); 4210 if (captions) 4211 if (!captionRemoved) 4212 textable.removeAt(1); 4213 for (const auto& s : textable) 4214 out << s; 4215 rowCount = 0; 4216 if (!captionRemoved) 4217 captionRemoved = true; 4218 } 4219 } 4220 } 4221 out << endTabularTable; 4222 } 4223 if (latexHeaders) 4224 out << QLatin1String("\\end{document} \n"); 4225 4226 if (!exportEntire) { 4227 qDeleteAll(toExport); 4228 toExport.clear(); 4229 } else 4230 toExport.clear(); 4231 } 4232 4233 void SpreadsheetView::exportToFits(const QString& fileName, const int exportTo, const bool commentsAsUnits) const { 4234 auto* filter = new FITSFilter; 4235 4236 filter->setExportTo(exportTo); 4237 filter->setCommentsAsUnits(commentsAsUnits); 4238 filter->write(fileName, m_spreadsheet); 4239 4240 delete filter; 4241 } 4242 4243 void SpreadsheetView::exportToXLSX(const QString& fileName, const bool exportHeader) const { 4244 auto* filter = new XLSXFilter; 4245 4246 DEBUG("EXPORT HEADER = " << exportHeader) 4247 filter->setColumnNamesAsFirstRow(exportHeader); 4248 filter->write(fileName, m_spreadsheet); 4249 4250 delete filter; 4251 } 4252 4253 void SpreadsheetView::exportToSQLite(const QString& path) const { 4254 QFile file(path); 4255 if (!file.open(QFile::WriteOnly | QFile::Truncate)) 4256 return; 4257 4258 PERFTRACE(QStringLiteral("export spreadsheet to SQLite database")); 4259 QApplication::processEvents(QEventLoop::AllEvents, 0); 4260 4261 // create database 4262 const QStringList& drivers = QSqlDatabase::drivers(); 4263 QString driver; 4264 if (drivers.contains(QLatin1String("QSQLITE3"))) 4265 driver = QLatin1String("QSQLITE3"); 4266 else 4267 driver = QLatin1String("QSQLITE"); 4268 4269 QSqlDatabase db = QSqlDatabase::addDatabase(driver); 4270 db.setDatabaseName(path); 4271 if (!db.open()) { 4272 RESET_CURSOR; 4273 KMessageBox::error(nullptr, i18n("Couldn't create the SQLite database %1.", path)); 4274 } 4275 4276 // create table 4277 const int cols = m_spreadsheet->columnCount(); 4278 QString query = QLatin1String("create table ") + m_spreadsheet->name() + QLatin1String(" ("); 4279 for (int i = 0; i < cols; ++i) { 4280 Column* col = m_spreadsheet->column(i); 4281 if (i != 0) 4282 query += QLatin1String(", "); 4283 4284 query += QLatin1String("\"") + col->name() + QLatin1String("\" "); 4285 switch (col->columnMode()) { 4286 case AbstractColumn::ColumnMode::Double: 4287 query += QLatin1String("REAL"); 4288 break; 4289 case AbstractColumn::ColumnMode::Integer: 4290 case AbstractColumn::ColumnMode::BigInt: 4291 query += QLatin1String("INTEGER"); 4292 break; 4293 case AbstractColumn::ColumnMode::Text: 4294 case AbstractColumn::ColumnMode::Month: 4295 case AbstractColumn::ColumnMode::Day: 4296 case AbstractColumn::ColumnMode::DateTime: 4297 query += QLatin1String("TEXT"); 4298 break; 4299 } 4300 } 4301 query += QLatin1Char(')'); 4302 QSqlQuery q; 4303 if (!q.exec(query)) { 4304 RESET_CURSOR; 4305 KMessageBox::error(nullptr, i18n("Failed to create table in the SQLite database %1.", path) + QLatin1Char('\n') + q.lastError().databaseText()); 4306 db.close(); 4307 return; 4308 } 4309 4310 int maxRow = maxRowToExport(); 4311 if (maxRow < 0) { 4312 db.close(); 4313 return; 4314 } 4315 4316 // create bulk insert statement in batches of 10k rows 4317 { 4318 PERFTRACE(QStringLiteral("Insert the data")); 4319 q.exec(QLatin1String("BEGIN TRANSACTION;")); 4320 4321 // create the first part of the INSERT-statement without the values 4322 QString insertQuery = QStringLiteral("INSERT INTO '") + m_spreadsheet->name() + QStringLiteral("' ("); 4323 for (int i = 0; i < cols; ++i) { 4324 if (i != 0) 4325 insertQuery += QLatin1String(", "); 4326 insertQuery += QLatin1Char('\'') + m_spreadsheet->column(i)->name() + QLatin1Char('\''); 4327 } 4328 insertQuery += QLatin1String(") VALUES "); 4329 4330 // add values in chunks of 10k row 4331 int chunkSize = 10000; 4332 int chunks = std::ceil((double)maxRow / chunkSize); 4333 for (int chunk = 0; chunk < chunks; ++chunk) { 4334 query = insertQuery; 4335 for (int i = 0; i < chunkSize; ++i) { 4336 int row = chunk * chunkSize + i; 4337 if (row > maxRow) 4338 break; 4339 4340 if (i != 0) 4341 query += QLatin1String(","); 4342 4343 query += QLatin1Char('('); 4344 for (int j = 0; j < cols; ++j) { 4345 auto* col = m_spreadsheet->column(j); 4346 if (j != 0) 4347 query += QLatin1String(", "); 4348 4349 query += QLatin1Char('\'') + col->asStringColumn()->textAt(row) + QLatin1Char('\''); 4350 } 4351 query += QLatin1String(")"); 4352 } 4353 query += QLatin1Char(';'); 4354 4355 // insert values for the current chunk of data 4356 if (!q.exec(query)) { 4357 RESET_CURSOR; 4358 KMessageBox::error(nullptr, i18n("Failed to insert values into the table.")); 4359 QDEBUG(Q_FUNC_INFO << ", bulk insert error " << q.lastError().databaseText()); 4360 db.close(); 4361 return; 4362 } 4363 } 4364 4365 } // end of perf-trace scope 4366 4367 // commit the transaction and close the database 4368 q.exec(QLatin1String("COMMIT TRANSACTION;")); 4369 db.close(); 4370 }