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