SedSat3 1.1.6
Sediment Source Apportionment Tool - Advanced statistical methods for environmental pollution research
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
1#include "mainwindow.h"
2#include "ui_mainwindow.h"
3
4#include "QDebug"
5#include "QFileDialog"
6#include "QDir"
7#include "sourcesinkdata.h"
8#include "xlsxdocument.h"
9#include "xlsxchartsheet.h"
10#include "xlsxcellrange.h"
11#include "xlsxchart.h"
12#include "xlsxrichstring.h"
13#include "xlsxworkbook.h"
15#include "plotwindow.h"
16#include "QMessageBox"
17#include "Utilities.h"
18#include "QMap"
19#include "QJsonDocument"
21#include "elementstablemodel.h"
23#include "GA.h"
24#include "genericform.h"
25#include "ProgressWindow.h"
26#include "resultswindow.h"
27#include "aboutdialog.h"
28#include "resultsetitem.h"
29#include "resultitem.h"
30#include "selectsamples.h"
32#include "toolboxitem.h"
33#include "resulttableviewer.h"
34#include "optionsdialog.h"
35#include <QStandardPaths>
36//#include "MCMC.h"
37
38#ifdef _WIN32
39#define NOBYTE
40//#include <windows.h>
41
42//#pragma comment(lib "shell32.lib")
43#endif
44
45#define RECENT "SedSatrecentFiles.txt"
46
47#ifndef max_num_recent_files
48#define max_num_recent_files 15
49#endif
50
51
52#define version "1.1.6"
53#define date_compiled "12/20/2025"
54
55using namespace QXlsx;
56
57MainWindow::MainWindow(QWidget* parent)
58 : QMainWindow(parent),
59 ui(new Ui::MainWindow),
60 sinkSheet(-1),
61 plotter(nullptr),
62 columnViewModel(nullptr),
63 resultsViewModel(nullptr),
64 ResultscontextMenu(nullptr),
65 DeleteAction(nullptr),
66 selectedTreeItemType("None"),
67 treeItemChangedProgramatically(false),
68 conductor(nullptr)
69{
70 ui->setupUi(this);
71
72 // Set window icon
73 QIcon mainIcon(resourcesPath() + "Icons/CMBSource_Icon.png");
74 setWindowIcon(mainIcon);
75
76 // Initialize conductor
77 conductor = std::make_unique<Conductor>(this);
78 conductor->SetWorkingFolder(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
79
80 // Setup UI components
85
86 // Initialize other components
87 centralform.reset(ui->textBrowser);
88
89 // Load recent files
91}
92
94{
95 // Menu actions - File operations
96 connect(ui->actionImport_Elemental_Profile_from_Excel, &QAction::triggered,
98 connect(ui->actionSave_Project, &QAction::triggered,
100 connect(ui->actionSave_As, &QAction::triggered,
102 connect(ui->actionOpen_Project, &QAction::triggered,
104
105 // Menu actions - Plotting
106 connect(ui->actionRaw_Elemental_Profiles, &QAction::triggered,
108
109 // Menu actions - Data operations
110 connect(ui->actionConstituent_Properties, &QAction::triggered,
112 connect(ui->actionInclude_Exclude_Samples, &QAction::triggered,
114 connect(ui->actionOrganic_Matter_Size_correction, &QAction::triggered,
116
117 // Menu actions - Help and settings
118 connect(ui->actionAbout, &QAction::triggered,
120 connect(ui->actionOptions, &QAction::triggered,
122
123 // Toolbar buttons
124 connect(ui->ShowTabular, &QPushButton::clicked,
126 connect(ui->btnZoom, &QPushButton::clicked,
128
129 // Tree views
130 connect(ui->treeViewtools, &QTreeView::doubleClicked,
132 connect(ui->TreeView_Results, &QTreeView::doubleClicked,
134
135 // Results context menu
136 connect(ui->TreeView_Results, &QWidget::customContextMenuRequested,
138 connect(DeleteAction, &QAction::triggered,
140}
141
143{
144 // Load tools configuration from JSON
145 QString toolsPath = resourcesPath() + + "/tools.json";
146 qDebug() << "Loading tools from:" << toolsPath;
147
148 QJsonDocument tools = loadJson(toolsPath);
149 QStandardItemModel* toolsModel = ToQStandardItemModel(tools);
150 toolsModel->setHorizontalHeaderLabels(QStringList() << "Tools");
151
152 ui->treeViewtools->setModel(toolsModel);
153 ui->treeViewtools->setEditTriggers(QAbstractItemView::NoEditTriggers);
154
155 // Load forms structure
156 QString formsPath = resourcesPath() + "/forms_structures.json";
157 formsstructure = loadJson(formsPath);
158
159 // Configure tree view settings
160 ui->treeView->setSelectionMode(QAbstractItemView::MultiSelection);
161 ui->verticalLayout_3->setContentsMargins(0, 0, 0, 0);
162 ui->verticalLayout_3->setSpacing(0);
163
164 // If the frame has its own layout, reduce its margins too
165 if (ui->frame->layout())
166 {
167 ui->frame->layout()->setContentsMargins(2, 2, 2, 2);
168 ui->frame->layout()->setSpacing(2);
169 }
170
171 ui->frame->setVisible(false);
172}
173
175{
176 // Initialize results model
177 resultsViewModel.reset(new QStandardItemModel());
178 resultsViewModel->setHorizontalHeaderLabels(QStringList() << "Results");
179
180 ui->TreeView_Results->setModel(resultsViewModel.get());
181 ui->TreeView_Results->setContextMenuPolicy(Qt::CustomContextMenu);
182
183 // Setup context menu
184 ResultscontextMenu = new QMenu(ui->TreeView_Results);
185 DeleteAction = new QAction("Delete", ResultscontextMenu);
187}
188
190{
191 // Setup table view button
192 QIcon iconTable(resourcesPath()+"/Icons/table.png");
193 ui->ShowTabular->setIcon(iconTable);
194 ui->ShowTabular->setEnabled(false);
195
196 // Setup zoom button
197 QIcon iconZoom(resourcesPath()+"/Icons/Zoom_Extends.png");
198 ui->btnZoom->setIcon(iconZoom);
199 ui->btnZoom->setEnabled(false);
200
201 // Call existing toolbar population method
203}
204
206{
207 // plotter is added to layout, so Qt handles it
208 // ui is deleted explicitly as always
209 delete ui;
210}
211
212QString MainWindow::getSaveFilePath(bool promptUser)
213{
214 // Use existing project file name if available and not prompting
215 if (!promptUser && !ProjectFileName.isEmpty())
216 {
217 return ProjectFileName;
218 }
219
220 // Show save file dialog
221 QString fileName = QFileDialog::getSaveFileName(
222 this,
223 tr("Save Project"),
224 ProjectFileName, // Simplified - empty QString is fine
225 tr("CMB Source file (*.cmb);;All files (*.*)"),
226 nullptr,
227 QFileDialog::DontUseNativeDialog
228 );
229
230 // User cancelled
231 if (fileName.isEmpty())
232 {
233 return QString();
234 }
235
236 // Ensure .cmb extension
237 if (!fileName.toLower().endsWith(".cmb"))
238 {
239 fileName += ".cmb";
240 }
241
242 return fileName;
243}
244
246{
247 QJsonObject resultsJson;
248
249 if (!resultsViewModel)
250 {
251 return resultsJson;
252 }
253
254 for (int i = 0; i < resultsViewModel->rowCount(); ++i)
255 {
256 QStandardItem* item = resultsViewModel->item(i, 0);
257 if (!item)
258 {
259 continue;
260 }
261
262 ResultSetItem* resultSetItem = dynamic_cast<ResultSetItem*>(item);
263 if (!resultSetItem || !resultSetItem->result)
264 {
265 qWarning() << "Invalid result item at row" << i;
266 continue;
267 }
268
269 resultsJson[resultSetItem->text()] = resultSetItem->result->toJsonObject();
270 }
271
272 return resultsJson;
273}
274
276{
277 QJsonObject projectJson;
278
279 projectJson["Element Information"] = Data()->ElementInformationToJsonObject();
280 projectJson["Element Data"] = Data()->ElementDataToJsonObject();
281 projectJson["Target Group"] = QString::fromStdString(Data()->GetTargetGroup());
282 projectJson["Tools used"] = Data()->ToolsUsedToJsonObject();
283 projectJson["Options"] = Data()->OptionsToJsonObject();
284 projectJson["Results"] = buildResultsJson();
285
286 return projectJson;
287}
288
289bool MainWindow::saveProjectToFile(const QString& filePath)
290{
291 QFile file(filePath);
292
293 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
294 {
295 QMessageBox::critical(
296 this,
297 tr("Save Error"),
298 tr("Could not open file for writing:\n%1\n\nError: %2")
299 .arg(filePath)
300 .arg(file.errorString())
301 );
302 return false;
303 }
304
305 // Set working folder based on file location
306 QFileInfo fileInfo(file);
307 conductor->SetWorkingFolder(fileInfo.absolutePath());
308
309 // Build and write JSON
310 QJsonObject projectJson = buildProjectJson();
311 QJsonDocument jsonDocument(projectJson);
312
313 qint64 bytesWritten = file.write(jsonDocument.toJson());
314 file.close();
315
316 if (bytesWritten == -1)
317 {
318 QMessageBox::critical(
319 this,
320 tr("Save Error"),
321 tr("Error writing to file:\n%1\n\nError: %2")
322 .arg(filePath)
323 .arg(file.errorString())
324 );
325 return false;
326 }
327
328 // Update project state
329 ProjectFileName = filePath;
330 addToRecentFiles(filePath, true);
331
332 return true;
333}
334
336{
337 QString filePath = getSaveFilePath(false);
338
339 if (filePath.isEmpty())
340 {
341 return; // User cancelled
342 }
343
344 if (saveProjectToFile(filePath))
345 {
346 statusBar()->showMessage(tr("Project saved successfully"), 3000);
347 }
348}
349
351{
352 QString filePath = getSaveFilePath(true);
353
354 if (filePath.isEmpty())
355 {
356 return; // User cancelled
357 }
358
359 if (saveProjectToFile(filePath))
360 {
361 statusBar()->showMessage(tr("Project saved successfully"), 3000);
362 }
363}
364
366{
367 QString fileName = QFileDialog::getOpenFileName(
368 this,
369 tr("Open Project"),
370 ProjectFileName, // Start in same directory as current project
371 tr("CMB Source file (*.cmb);;All files (*.*)"),
372 nullptr,
373 QFileDialog::DontUseNativeDialog
374 );
375
376 if (fileName.isEmpty())
377 {
378 return;
379 }
380
381 if (LoadModel(fileName))
382 {
383 addToRecentFiles(fileName, true);
384 statusBar()->showMessage(tr("Project opened successfully"), 3000);
385 }
386}
387
388bool MainWindow::LoadModel(const QString& fileName)
389{
390 QFile file(fileName);
391
392 if (!file.open(QIODevice::ReadOnly))
393 {
394 QMessageBox::critical(
395 this,
396 tr("Load Error"),
397 tr("Could not open file for reading:\n%1\n\nError: %2")
398 .arg(fileName)
399 .arg(file.errorString())
400 );
401 return false;
402 }
403
404 // Read and parse JSON
405 QByteArray fileData = file.readAll();
406 file.close();
407
408 QJsonDocument jsonDoc = QJsonDocument::fromJson(fileData);
409 if (jsonDoc.isNull())
410 {
411 QMessageBox::critical(
412 this,
413 tr("Load Error"),
414 tr("Invalid JSON format in file:\n%1")
415 .arg(fileName)
416 );
417 return false;
418 }
419
420 QJsonObject jsonObject = jsonDoc.object();
421
422 // Set working directory
423 QFileInfo fileInfo(fileName);
424 conductor->SetWorkingFolder(fileInfo.absolutePath());
425
426 // Load data
427 file.open(QIODevice::ReadOnly);
428 Data()->ReadFromFile(&file);
429 file.close();
430
431 // Load results
432 QJsonObject resultsJson = jsonObject["Results"].toObject();
433 resultsViewModel->clear();
434 resultsViewModel->setHorizontalHeaderLabels(QStringList() << "Results");
435
436 for (const QString& key : resultsJson.keys())
437 {
438 ResultSetItem* resultSet = new ResultSetItem(key);
439 resultSet->setToolTip(key);
440 resultSet->result = new Results();
441 resultSet->result->ReadFromJson(resultsJson.value(key).toObject());
442
443 // Process MLR results
444 for (auto it = resultSet->result->begin(); it != resultSet->result->end(); ++it)
445 {
446 if (it->second.Type() == result_type::mlrset)
447 {
448 QStringList parts = QString::fromStdString(it->first).split("OM & Size MLR for ");
449 if (parts.size() > 1)
450 {
451 std::string source = parts[1].toStdString();
452 if (Data()->count(source) > 0)
453 {
454 Data()->at(source).SetRegressionModels(
455 static_cast<MultipleLinearRegressionSet*>(it->second.Result())
456 );
458 Data()->at(source).GetRegressionModels()->begin()->second.GetIndependentVariableNames()
459 );
460 }
461 }
462 }
463 }
464
465 resultsViewModel->appendRow(resultSet);
466 }
467
468 // Finalize data
472
473 // Update UI state
474 ProjectFileName = fileName;
475 setWindowTitle("SedSat3: " + ProjectFileName);
476
477 ui->ShowTabular->setEnabled(true);
478 ui->btnZoom->setEnabled(true);
479 return true;
480}
481
483{
484 QString fileName = QFileDialog::getOpenFileName(
485 this,
486 tr("Open Excel File"),
487 ProjectFileName.isEmpty() ? "" : QFileInfo(ProjectFileName).absolutePath(),
488 tr("Excel files (*.xlsx);;All files (*.*)"),
489 nullptr,
490 QFileDialog::DontUseNativeDialog
491 );
492
493 if (fileName.isEmpty())
494 {
495 return; // User cancelled
496 }
497
498 QFileInfo fileInfo(fileName);
499 conductor->SetWorkingFolder(fileInfo.absolutePath());
500
501 if (ReadExcel(fileName))
502 {
504
505 QMessageBox::information(
506 this,
507 tr("Element Selection"),
508 tr("To exclude elements from analysis, double-click on an element and uncheck the box."),
509 QMessageBox::Ok
510 );
511
513 }
514}
516{
517 // Create new model
518 columnViewModel = std::make_unique<QStandardItemModel>();
519 columnViewModel->setColumnCount(1);
520 columnViewModel->setHorizontalHeaderLabels(QStringList() << "Data");
521
522 // Add samples item
523 QList<QStandardItem*> modelItems;
524 modelItems.append(ToQStandardItem("Samples", &dataCollection));
525 columnViewModel->appendRow(modelItems);
526
527 // Add elements item
528 QList<QStandardItem*> elementItems;
529 elementItems.append(ElementsToQStandardItem("Elements", &dataCollection));
530 columnViewModel->appendRow(elementItems);
531
532 // Set model on tree view
533 ui->treeView->setModel(columnViewModel.get());
534 ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
535
536 // Connect signals - modern Qt5 syntax
537 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
539 connect(ui->treeView, &QWidget::customContextMenuRequested,
541}
543{
544 PlotWindow *plotwindow = new PlotWindow(this);
545 plotwindow->show();
546}
547
548bool MainWindow::ReadExcel(const QString& filename)
549{
550 // Load Excel file
551 Document xlsxR(filename);
552 if (!xlsxR.load())
553 {
554 QMessageBox::critical(
555 this,
556 tr("Excel Load Error"),
557 tr("Failed to load Excel file:\n%1").arg(filename)
558 );
559 return false;
560 }
561
562 qDebug() << "[debug] Successfully loaded xlsx file.";
563 QStringList allSheetNames = xlsxR.sheetNames();
564
565 // Clear existing data
567
568 // Let user select sheets to import
569 QStringList* sheetsToInclude = nullptr;
570 auto chooseSheetsDlg = std::make_unique<DialogChooseExcelSheets>(this, &sheetsToInclude);
571
572 for (const QString& sheetName : allSheetNames)
573 {
574 chooseSheetsDlg->AddItem(sheetName);
575 qDebug() << sheetName;
576 }
577
578 chooseSheetsDlg->exec();
579
580 if (!sheetsToInclude)
581 {
582 return false; // User cancelled
583 }
584
585 // Filter selected sheets
586 QStringList selectedSheets;
587 for (const QString& sheetName : allSheetNames)
588 {
589 if (sheetsToInclude->contains(sheetName))
590 {
591 selectedSheets << sheetName;
592 }
593 }
594 delete sheetsToInclude;
595
596 if (selectedSheets.isEmpty())
597 {
598 return false; // No sheets selected
599 }
600
601 // Let user indicate which sheet is the sink
602 auto indicateSheetDlg = std::make_unique<IndicateSheetsDialog>(this);
603 indicateSheetDlg->Populate_Table(selectedSheets);
604 indicateSheetDlg->exec();
605
606 qDebug() << "Sink Sheet is:" << sinkSheet;
607
608 // Validate element names are consistent across all sheets
609 QList<QStringList> elementNamesBySheet;
610
611 for (int sheetIdx = 0; sheetIdx < selectedSheets.count(); ++sheetIdx)
612 {
613 xlsxR.selectSheet(selectedSheets[sheetIdx]);
614 QStringList elementNames;
615
616 // Read element names from row 1, starting at column 2
617 int col = 2;
618 while (true)
619 {
620 auto cell = xlsxR.cellAt(1, col); // Returns shared_ptr
621 if (!cell)
622 break;
623
624 QString elemName = cell->readValue().toString().trimmed();
625 if (elemName.isEmpty())
626 break;
627
628 elementNames.append(elemName);
629 qDebug() << elemName;
630 col++;
631 }
632
633 // Verify element names match across sheets
634 if (sheetIdx > 0 && elementNames != elementNamesBySheet.last())
635 {
636 QMessageBox::warning(
637 this,
638 tr("Element Mismatch"),
639 tr("Element names and their order must be identical in all sheets.\n\n"
640 "Mismatch found between:\n '%1'\n '%2'")
641 .arg(selectedSheets[sheetIdx - 1])
642 .arg(selectedSheets[sheetIdx])
643 );
644
646 QString("Element mismatch between sheets '%1' and '%2'")
647 .arg(selectedSheets[sheetIdx - 1])
648 .arg(selectedSheets[sheetIdx]),
649 Qt::red
650 );
651 return false;
652 }
653
654 elementNamesBySheet.append(elementNames);
655 }
656
657 // Read sample data from each sheet
658 for (int sheetIdx = 0; sheetIdx < selectedSheets.count(); ++sheetIdx)
659 {
660 xlsxR.selectSheet(selectedSheets[sheetIdx]);
661
663 selectedSheets[sheetIdx].toStdString()
664 );
665
666 // Read samples starting from row 2
667 int row = 2;
668 while (true)
669 {
670 auto sampleCell = xlsxR.cellAt(row, 1);
671 if (!sampleCell || sampleCell->readValue().toString().isEmpty())
672 break;
673
674 QString sampleName = sampleCell->readValue().toString();
675 Elemental_Profile elementalProfile;
676
677 // Read element values for this sample
678 for (int col = 0; col < elementNamesBySheet[0].count(); ++col)
679 {
680 auto dataCell = xlsxR.cellAt(row, col + 2);
681
682 if (!dataCell)
683 {
684 QMessageBox::warning(
685 this,
686 tr("Empty Cell"),
687 tr("Empty cell found in:\n"
688 "Sheet: %1\n"
689 "Row: %2\n"
690 "Column: %3")
691 .arg(selectedSheets[sheetIdx])
692 .arg(row)
693 .arg(col + 2)
694 );
696 return false;
697 }
698
699 bool isNumber = false;
700 double value = dataCell->readValue().toDouble(&isNumber);
701
702 if (!isNumber)
703 {
704 QMessageBox::warning(
705 this,
706 tr("Invalid Data"),
707 tr("Non-numerical value found:\n"
708 "Sheet: %1\n"
709 "Row: %2\n"
710 "Column: %3\n"
711 "Value: '%4'")
712 .arg(selectedSheets[sheetIdx])
713 .arg(row)
714 .arg(col + 2)
715 .arg(dataCell->readValue().toString())
716 );
718 return false;
719 }
720
721 elementalProfile.AppendElement(
722 elementNamesBySheet[0][col].toStdString(),
723 value
724 );
725 }
726
727 profileSet->AppendProfile(sampleName.toStdString(), elementalProfile);
728 row++;
729 }
730 }
731
732 // Finalize data collection
736
737 qDebug() << "Reading element information done!";
738
739 return true;
740}
741
742void MainWindow::WriteMessageOnScreen(const QString &text, QColor color)
743{
744 ui->textBrowser->setTextColor(color);
745 ui->textBrowser->append(text);
746}
747
748QStandardItem* MainWindow::ToQStandardItem(const QString& key, const SourceSinkData* srcsinkdata)
749{
750 QStandardItem* rootItem = new QStandardItem(key);
751 rootItem->setData("RootItem", Qt::UserRole);
752
753 vector<string> groupNames = dataCollection.GetGroupNames();
754
755 for (size_t i = 0; i < groupNames.size(); ++i)
756 {
757 QStandardItem* groupItem = new QStandardItem(QString::fromStdString(groupNames[i]));
758 groupItem->setData("Group", Qt::UserRole);
759 groupItem->setData(QString::fromStdString(groupNames[i]), groupRole);
760
761 Elemental_Profile_Set* sampleSet = dataCollection.GetSampleSet(groupNames[i]);
762 if (sampleSet)
763 {
764 for (auto it = sampleSet->begin(); it != sampleSet->end(); ++it)
765 {
766 QStandardItem* sampleItem = new QStandardItem(QString::fromStdString(it->first));
767 sampleItem->setData("Sample", Qt::UserRole);
768 sampleItem->setData(QString::fromStdString(groupNames[i]), groupRole);
769 sampleItem->setData(QString::fromStdString(it->first), sampleRole);
770
771 groupItem->appendRow(sampleItem);
772 }
773 }
774
775 rootItem->appendRow(groupItem);
776 }
777
778 return rootItem;
779}
780
781QStandardItem* MainWindow::ElementsToQStandardItem(const QString& key, const SourceSinkData* srcsinkdata)
782{
783 QStandardItem* rootItem = new QStandardItem(key);
784 rootItem->setData("RootItem", Qt::UserRole);
785
786 vector<string> groupNames = dataCollection.GetGroupNames();
787 vector<string> elementNames = dataCollection.GetElementNames();
788
789 for (size_t elemIdx = 0; elemIdx < elementNames.size(); ++elemIdx)
790 {
791 QStandardItem* elementItem = new QStandardItem(QString::fromStdString(elementNames[elemIdx]));
792 elementItem->setData("Element", Qt::UserRole);
793 elementItem->setData(QString::fromStdString(elementNames[elemIdx]), elementRole);
794
795 for (size_t groupIdx = 0; groupIdx < groupNames.size(); ++groupIdx)
796 {
797 QStandardItem* groupItem = new QStandardItem(QString::fromStdString(groupNames[groupIdx]));
798 groupItem->setData("GroupInElements", Qt::UserRole);
799 groupItem->setData(QString::fromStdString(elementNames[elemIdx]), elementRole);
800 groupItem->setData(QString::fromStdString(groupNames[groupIdx]), groupRole);
801
802 elementItem->appendRow(groupItem);
803 }
804
805 rootItem->appendRow(elementItem);
806 }
807
808 return rootItem;
809}
810
811QStandardItemModel* MainWindow::ToQStandardItemModel(const SourceSinkData* srcsinkdata)
812{
813 columnViewModel = std::make_unique<QStandardItemModel>();
814 columnViewModel->setColumnCount(1);
815 columnViewModel->setHorizontalHeaderLabels(QStringList() << "Groups");
816
817 vector<string> groupNames = dataCollection.GetGroupNames();
818
819 for (size_t i = 0; i < groupNames.size(); ++i)
820 {
821 QStandardItem* groupItem = new QStandardItem(QString::fromStdString(groupNames[i]));
822 groupItem->setData("Parent", Qt::UserRole);
823
824 Elemental_Profile_Set* sampleSet = dataCollection.GetSampleSet(groupNames[i]);
825 if (sampleSet)
826 {
827 for (auto it = sampleSet->begin(); it != sampleSet->end(); ++it)
828 {
829 QStandardItem* sampleItem = new QStandardItem(QString::fromStdString(it->first));
830 sampleItem->setData(QString::fromStdString(it->first), elementRole);
831 sampleItem->setData(QString::fromStdString(groupNames[i]), groupRole);
832 sampleItem->setData("Child", Qt::UserRole);
833
834 groupItem->appendRow(sampleItem);
835 }
836 }
837
838 columnViewModel->appendRow(groupItem);
839 }
840
841 return columnViewModel.get();
842}
843void MainWindow::on_tree_selectionChanged(const QItemSelection &changed)
844{
845 qDebug()<<"Selection changed "<<changed;
846 ui->ShowTabular->setEnabled(false);
848 if (plotter==nullptr)
849 {
850#ifdef USE_QCHARTS
851 plotter = new GeneralChartPlotter(this);
852#else
853 plotter = new GeneralPlotter(this);
854#endif // USE_QCHARTS
855
856 ui->verticalLayout_3->addWidget(plotter);
857
858 }
859 plotter->Clear();
860 enum class plottype {elemental_profiles, element_scatters} PlotType = plottype::elemental_profiles;
861 vector<vector<string>> samples_selected;
862 QModelIndexList indexes = ui->treeView->selectionModel()->selectedIndexes();
863 if (changed.size()>0)
864 { QModelIndex changed_index = changed.at(0).indexes()[0];
865 if (indexes.size()>0)
866 { if (indexes.contains(changed.at(0).indexes()[0]))
867 if (changed.at(0).indexes()[0].data(Qt::UserRole)!=selectedTreeItemType && selectedTreeItemType!="None")
868 {
869 selectedTreeItemType = changed.at(0).indexes()[0].data(Qt::UserRole).toString();
871 ui->treeView->selectionModel()->clearSelection();
872 ui->treeView->selectionModel()->select(changed_index,QItemSelectionModel::Select | QItemSelectionModel::Rows);
874 indexes = ui->treeView->selectionModel()->selectedIndexes();
875 }
876 }
877 }
878 for (int i=0; i<indexes.size(); i++)
879 {
880 qDebug()<<indexes[i].data(Qt::UserRole).toString();
881 if (indexes[i].data(Qt::UserRole).toString()=="Group")
882 {
883 QString Group_Name_Selected = indexes[i].data().toString();
884 vector<string> Sample_Names = dataCollection.GetSampleSet(Group_Name_Selected.toStdString())->GetSampleNames();
885 for (int sample_counter=0; sample_counter<Sample_Names.size(); sample_counter++)
886 {
887 vector<string> item;
888 item.push_back(indexes[i].data().toString().toStdString());
889 item.push_back(ui->treeView->selectionModel()->model()->index(sample_counter,0, indexes[i]).data().toString().toStdString());
890 samples_selected.push_back(item);
891 }
892 }
893 else if (indexes[i].data(Qt::UserRole).toString()=="Sample")
894 {
895 vector<string> item;
896 item.push_back(indexes[i].parent().data().toString().toStdString());
897 item.push_back(indexes[i].data().toString().toStdString());
898 samples_selected.push_back(item);
899 }
900 if (indexes[i].data(Qt::UserRole).toString()=="Element")
901 {
902 qDebug()<<"Element selected!";
903 PlotType = plottype::element_scatters;
904 QString Element_Name_Selected = indexes[i].data().toString();
905 map<string,vector<double>> extracted_data = dataCollection.ExtractElementDataByGroup(Element_Name_Selected.toStdString());
906 qDebug()<<"Data extracted!";
907 if (plotter==nullptr)
908 {
909#ifdef USE_QCHARTS
910 plotter = new GeneralChartPlotter(this);
911#else
912 plotter = new GeneralPlotter(this);
913#endif // USE_QCHARTS
914 ui->verticalLayout_3->addWidget(plotter);
915
916 }
917
918 plotter->AddNonUniformScatter(extracted_data,i,8,Element_Name_Selected.toStdString());
919#ifdef USE_QCHARTS
920 plotter->SetYAxisScaleType(ChartAxisScale::Logarithmic);
921#else
923#endif // USE_QCHARTS
924
925 }
926 }
927
928
929 if (samples_selected.size()>0 && PlotType==plottype::elemental_profiles)
930 { profiles_data extracted_data = dataCollection.ExtractConcentrationData(samples_selected);
931 plottedData = std::make_unique<Elemental_Profile_Set>(
933 );
934 ui->ShowTabular->setEnabled(true);
935 plotter->AddScatters(extracted_data.sample_names,extracted_data.element_names, extracted_data.values);
936#ifdef USE_QCHARTS
937 plotter->SetYAxisScaleType(ChartAxisScale::Logarithmic);
938#else
940#endif // USE_QCHARTS
941 }
942 ui->frame->setVisible(true);
943 ui->frame->setEnabled(true);
944 ui->btnZoom->setEnabled(true);
946}
947
948QJsonDocument MainWindow::loadJson(const QString &fileName) {
949 QFile jsonFile(fileName);
950 jsonFile.open(QFile::ReadOnly);
951 return QJsonDocument().fromJson(jsonFile.readAll());
952}
953
954void MainWindow::saveJson(const QJsonDocument &document, const QString &fileName) {
955 QFile jsonFile(fileName);
956 jsonFile.open(QFile::WriteOnly);
957 jsonFile.write(document.toJson());
958}
959
960QStandardItem* MainWindow::ToQStandardItem(const QString& key, const QJsonObject& json)
961{
962 QStandardItem* standardItem = new QStandardItem(key);
963
964 QStringList keys = json.keys();
965 for (const QString& jsonKey : keys)
966 {
967 if (json[jsonKey].isObject())
968 {
969 // Recursively create items for nested objects
970 standardItem->appendRow(ToQStandardItem(jsonKey, json[jsonKey].toObject()));
971 standardItem->setToolTip(jsonKey);
972 }
973 else
974 {
975 // Create leaf items for values
976 QStandardItem* subItem = new ToolBoxItem(Data(), jsonKey);
977 subItem->setData(json.value(jsonKey).toString(), Qt::UserRole);
978 subItem->setData(json.value(jsonKey).toString(), Qt::UserRole + 1);
979 subItem->setToolTip(jsonKey);
980 standardItem->appendRow(subItem);
981 }
982 }
983
984 return standardItem;
985}
986
987QStandardItemModel* MainWindow::ToQStandardItemModel(const QJsonDocument& jsonDocument)
988{
989 QStandardItemModel* standardItemModel = new QStandardItemModel();
990 QJsonObject jsonObject = jsonDocument.object();
991 QStandardItem* rootItem = ToQStandardItem("Tools", jsonObject);
992
993 standardItemModel->appendRow(rootItem);
994
995 return standardItemModel;
996}
997
999{
1000 QModelIndexList selectedIndexes = ui->treeView->selectionModel()->selectedIndexes();
1001
1002 if (!selectedIndexes.isEmpty())
1003 {
1004 return selectedIndexes[0].data(Qt::UserRole).toString();
1005 }
1006
1007 return "none";
1008}
1009
1010void MainWindow::preparetreeviewMenu(const QPoint& pos)
1011{
1012 QTreeView* tree = ui->treeView;
1013 QModelIndex index = tree->currentIndex();
1014
1015 if (!index.isValid())
1016 {
1017 return;
1018 }
1019
1020 qDebug() << index.data(elementRole);
1021 qDebug() << index.data(groupRole);
1022 qDebug() << index.data(sampleRole);
1023
1024 menu = std::make_unique<QMenu>(this);
1025
1026 if (!index.data(elementRole).toString().isEmpty())
1027 {
1028 QAction* showDistributions = menu->addAction("Show fitted distributions");
1029
1030 QStringList roleData;
1031 roleData << "Element=" + index.data(elementRole).toString();
1032 roleData << "Group=" + index.data(groupRole).toString();
1033 roleData << "Sample=" + index.data(sampleRole).toString();
1034 roleData << "Action=SFD";
1035
1036 showDistributions->setData(roleData);
1037
1038 connect(showDistributions, &QAction::triggered,
1040 }
1041
1042 if (menu)
1043 {
1044 menu->exec(tree->mapToGlobal(pos));
1045 }
1046}
1047
1049{
1050 QAction* action = qobject_cast<QAction*>(sender());
1051 if (!action)
1052 {
1053 return;
1054 }
1055
1056 QStringList keysStringList = action->data().toStringList();
1057 QMap<QString, QString> keys;
1058
1059 for (const QString& keyValue : keysStringList)
1060 {
1061 QStringList parts = keyValue.split("=");
1062 if (parts.size() == 2)
1063 {
1064 keys[parts[0]] = parts[1];
1065 }
1066 }
1067
1068 QString element = keys["Element"];
1069 QString group = keys["Group"];
1070 QString sample = keys["Sample"];
1071 QString actionType = keys["Action"];
1072
1073 if (actionType != "SFD")
1074 {
1075 return;
1076 }
1077
1078 PlotWindow* plotWindow = new PlotWindow(this);
1079
1080 if (group.isEmpty())
1081 {
1082 // Plot distribution for all samples
1083 TimeSeries<double> elementDist =
1085
1086 plotWindow->Plotter()->AddTimeSeries(
1087 "All samples",
1088 elementDist.tToStdVector(),
1089 elementDist.ValuesToStdVector()
1090 );
1091
1092 // Add distribution for each group
1093 for (auto it = dataCollection.begin(); it != dataCollection.end(); ++it)
1094 {
1095 elementDist = dataCollection.GetSampleSet(it->first)
1096 ->GetElementDistribution(element.toStdString())
1097 ->GetFittedDistribution()
1098 ->EvaluateAsTimeSeries();
1099
1100 plotWindow->Plotter()->AddTimeSeries(
1101 it->first,
1102 elementDist.tToStdVector(),
1103 elementDist.ValuesToStdVector()
1104 );
1105 }
1106 }
1107 else
1108 {
1109 // Plot distribution for specific group
1110 TimeSeries<double> elementDist =
1111 dataCollection.GetSampleSet(group.toStdString())
1112 ->GetElementDistribution(element.toStdString())
1113 ->GetFittedDistribution()
1114 ->EvaluateAsTimeSeries();
1115
1116 plotWindow->Plotter()->AddTimeSeries(
1117 (group + ":" + element).toStdString(),
1118 elementDist.tToStdVector(),
1119 elementDist.ValuesToStdVector()
1120 );
1121 }
1122
1123 plotWindow->setWindowTitle(element);
1124 plotWindow->Plotter()->SetLegend(true);
1125 plotWindow->show();
1126}
1127
1129{
1130 FormElementInformation* formElems = new FormElementInformation(this);
1131 ElementTableModel* elementTableModel = new ElementTableModel(&dataCollection, this);
1132 formElems->table()->setModel(elementTableModel);
1133
1134 ElementTableDelegate* elemDelegate = new ElementTableDelegate(&dataCollection, this);
1135 formElems->table()->setItemDelegate(elemDelegate);
1136
1137 ui->verticalLayout_middle->addWidget(formElems);
1138 centralform.reset(formElems);
1139}
1140
1142{
1143 double outlierThreshold = Data()->GetOptions()->operator[]("Outlier deviation threshold");
1144
1145 // Prompt for options if using default threshold
1146 if (outlierThreshold == 3)
1147 {
1149 }
1150
1151 // Perform outlier analysis
1152 Data()->OutlierAnalysisForAll(-outlierThreshold, outlierThreshold);
1153 Data()->BracketTest(false, false, false);
1154
1155 // Create and configure sample selection dialog
1156 SelectSamples* sampleSelector = new SelectSamples(this);
1157 sampleSelector->SetMode(mode::samples);
1158 sampleSelector->SetData(&dataCollection);
1159
1160 ui->verticalLayout_middle->addWidget(sampleSelector);
1161 centralform.reset(sampleSelector);
1162}
1163
1165{
1166 // Check if OM and size correction has been performed
1167 if (dataCollection.OMandSizeConstituents()[0].empty() &&
1169 {
1170 QMessageBox::warning(
1171 this,
1172 tr("SedSat3"),
1173 tr("Organic Matter and Size Correction must be performed first."),
1174 QMessageBox::Ok
1175 );
1176 return;
1177 }
1178
1179 // Create and configure sample selection dialog
1180 SelectSamples* sampleSelector = new SelectSamples(this);
1181 sampleSelector->SetMode(mode::regressions);
1182 sampleSelector->SetData(&dataCollection);
1183
1184 ui->verticalLayout_middle->addWidget(sampleSelector);
1185 centralform.reset(sampleSelector);
1186}
1187
1188
1189void MainWindow::on_tool_executed(const QModelIndex& index)
1190{
1191 qDebug() << "Tool executed" << index.data() << ":" << index.data(Qt::UserRole);
1192
1193 // Check if data has been loaded
1194 if (dataCollection.GetElementNames().empty())
1195 {
1196 QMessageBox::warning(
1197 this,
1198 tr("SedSat3"),
1199 tr("No data has been loaded."),
1200 QMessageBox::Ok
1201 );
1202 return;
1203 }
1204
1205 QJsonObject mainJsonObject = formsstructure.object();
1206 QString toolName = index.data(Qt::UserRole).toString();
1207
1208 if (mainJsonObject.contains(toolName))
1209 {
1210 qDebug() << toolName;
1211
1212 QJsonObject formObject = mainJsonObject.value(toolName).toObject();
1213
1214 centralform = std::make_unique<GenericForm>(&formObject, this, this);
1215 dynamic_cast<GenericForm*>(centralform.get())->SetCommand(toolName);
1216 ui->verticalLayout_middle->addWidget(centralform.get());
1217 }
1218}
1219
1220void MainWindow::on_old_result_requested(const QModelIndex& index)
1221{
1222 if (!index.isValid() || !resultsViewModel)
1223 {
1224 return;
1225 }
1226
1227 QStandardItem* item = resultsViewModel->item(index.row());
1228 if (!item)
1229 {
1230 return;
1231 }
1232
1233 ResultSetItem* resultSetItem = dynamic_cast<ResultSetItem*>(item);
1234 if (!resultSetItem || !resultSetItem->result)
1235 {
1236 qWarning() << "Invalid result item at row" << index.row();
1237 return;
1238 }
1239
1240 Results* resultSet = resultSetItem->result;
1241
1242 ResultsWindow* resultsWindow = new ResultsWindow(this);
1243 resultsWindow->SetResults(resultSet);
1244 resultsWindow->setWindowTitle(resultSetItem->text());
1245
1246 for (auto it = resultSet->begin(); it != resultSet->end(); ++it)
1247 {
1248 resultsWindow->AppendResult(it->second);
1249 }
1250
1251 resultsWindow->show();
1252}
1253
1254bool MainWindow::Execute(const std::string& command, std::map<std::string, std::string> arguments)
1255{
1256 conductor->SetData(&dataCollection);
1257
1258 bool outcome = conductor->Execute(command, arguments);
1259 if (!outcome)
1260 {
1261 return false;
1262 }
1263
1264 // Create separate copies for window and permanent storage
1265 Results* resultsForWindow = conductor->GetResults(); // For ResultsWindow
1266 Results* resultsForStorage = conductor->GetResults(); // For ResultSetItem (persists)
1267
1268 // Generate timestamp suffix
1269 QString timestamp = QDateTime::currentDateTime().toString(Qt::TextDate);
1270 QString resultName = QString::fromStdString(resultsForStorage->GetName()) + "_" + timestamp;
1271
1272 // Create result set item for permanent storage
1273 ResultSetItem* resultSetItem = new ResultSetItem(resultName);
1274 resultSetItem->setToolTip(resultName);
1275 resultSetItem->result = resultsForStorage;
1276 resultsViewModel->appendRow(resultSetItem);
1277
1278 // Create results window
1279 ResultsWindow* resultsWindow = new ResultsWindow(this);
1280 resultsWindow->SetResults(resultsForWindow);
1281 resultsWindow->setWindowTitle(resultName);
1282
1283 // Populate results window
1284 for (auto it = resultsForStorage->begin(); it != resultsForStorage->end(); ++it)
1285 {
1286 resultsWindow->AppendResult(it->second);
1287 }
1288
1289 resultsWindow->show();
1290
1291 return true;
1292}
1293
1295{
1296 AboutDialog* aboutDlg = new AboutDialog(this);
1297 aboutDlg->setVersion(version);
1298 aboutDlg->setBuildDate(date_compiled);
1299 aboutDlg->exec();
1300 delete aboutDlg;
1301}
1302
1303void MainWindow::onCustomContextMenu(const QPoint& point)
1304{
1305 indexresultselected = ui->TreeView_Results->indexAt(point);
1306 if (indexresultselected.isValid()) {
1307 ResultscontextMenu->exec(ui->TreeView_Results->viewport()->mapToGlobal(point));
1308 }
1309}
1310
1312{
1313 if (!indexresultselected.isValid())
1314 {
1315 qWarning() << "Attempted to delete with invalid index";
1316 return;
1317 }
1318
1319 QStandardItem* item = resultsViewModel->item(indexresultselected.row());
1320 if (item)
1321 {
1322 int ret = QMessageBox::question(
1323 this,
1324 tr("Delete Result"),
1325 tr("Are you sure you want to delete '%1'?").arg(item->text()),
1326 QMessageBox::Yes | QMessageBox::No,
1327 QMessageBox::No
1328 );
1329
1330 if (ret == QMessageBox::Yes)
1331 {
1332 resultsViewModel->removeRow(indexresultselected.row());
1333 }
1334 }
1335}
1336
1338{
1339 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1340 + "/" + RECENT;
1341
1342 QFile file(recentFilePath);
1343 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1344 {
1345 qDebug() << "No recent files list found";
1346 return;
1347 }
1348
1349 QTextStream in(&file);
1350 QStringList allLines;
1351
1352 while (!in.atEnd())
1353 {
1354 QString line = in.readLine().trimmed();
1355 if (!line.isEmpty())
1356 {
1357 allLines.append(line);
1358 }
1359 }
1360 file.close();
1361
1362 // Add only the most recent files (up to max_num_recent_files)
1363 int startIndex = qMax(0, allLines.size() - max_num_recent_files);
1364 for (int i = startIndex; i < allLines.size(); ++i)
1365 {
1366 addToRecentFiles(allLines[i], false);
1367 }
1368}
1369
1370void MainWindow::addToRecentFiles(const QString& fileName, bool addToFile)
1371{
1372 if (fileName.trimmed().isEmpty())
1373 {
1374 return;
1375 }
1376
1377 QString trimmedFileName = fileName.trimmed();
1378 bool rewriteFile = false;
1379
1380 // If file already exists in list, move it to the end (most recent)
1381 if (recentFiles.contains(trimmedFileName))
1382 {
1383 int existingIndex = recentFiles.indexOf(trimmedFileName);
1384
1385 // Only move if not already at the end
1386 if (existingIndex != recentFiles.count() - 1)
1387 {
1388 // Remove from menu
1389 QList<QAction*> actions = ui->menuRecent->actions();
1390 int actionIndex = recentFiles.size() - 1 - existingIndex;
1391 if (actionIndex >= 0 && actionIndex < actions.size())
1392 {
1393 ui->menuRecent->removeAction(actions[actionIndex]);
1394 }
1395
1396 // Remove from list
1397 recentFiles.removeOne(trimmedFileName);
1398 addToFile = false;
1399 rewriteFile = true;
1400 }
1401 else
1402 {
1403 return; // Already most recent, nothing to do
1404 }
1405 }
1406
1407 // Enforce maximum number of recent files
1408 if (recentFiles.size() >= max_num_recent_files)
1409 {
1410 // Remove oldest file (first in list)
1411 recentFiles.removeFirst();
1412
1413 // Remove oldest action (last in menu)
1414 QList<QAction*> actions = ui->menuRecent->actions();
1415 if (!actions.isEmpty())
1416 {
1417 ui->menuRecent->removeAction(actions.last());
1418 }
1419
1420 rewriteFile = true;
1421 }
1422
1423 // Add to list
1424 recentFiles.append(trimmedFileName);
1425
1426 // Add to menu (at the top)
1427 QAction* fileAction = new QAction(trimmedFileName, this);
1428
1429 QList<QAction*> actions = ui->menuRecent->actions();
1430 if (!actions.isEmpty())
1431 {
1432 ui->menuRecent->insertAction(actions.first(), fileAction);
1433 }
1434 else
1435 {
1436 ui->menuRecent->addAction(fileAction);
1437 }
1438
1439 connect(fileAction, &QAction::triggered, this, &MainWindow::on_actionRecent_triggered);
1440
1441 // Write to file if needed
1442 if (addToFile)
1443 {
1444 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1445 + "/" + RECENT;
1446 CreateFileIfDoesNotExist(recentFilePath);
1447
1448 QFile file(recentFilePath);
1449 if (file.open(QIODevice::Append | QIODevice::Text))
1450 {
1451 QTextStream out(&file);
1452 out << trimmedFileName << "\n";
1453 file.close();
1454 }
1455 }
1456
1457 if (rewriteFile)
1458 {
1460 }
1461}
1462
1464{
1465 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1466 + "/" + RECENT;
1467
1468 QFile file(recentFilePath);
1469 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
1470 {
1471 qWarning() << "Failed to write recent files list:" << file.errorString();
1472 return;
1473 }
1474
1475 QTextStream out(&file);
1476 for (const QString& fileName : recentFiles)
1477 {
1478 out << fileName << "\n";
1479 }
1480
1481 file.close();
1482}
1483
1485{
1486 QString localAppDataPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
1487 qDebug() << "Local AppData Folder:" << localAppDataPath;
1488 return localAppDataPath;
1489}
1490
1492{
1493 QAction* action = qobject_cast<QAction*>(sender());
1494 if (!action)
1495 {
1496 qWarning() << "Invalid sender in on_actionRecent_triggered";
1497 return;
1498 }
1499
1500 QString fileName = action->text();
1501 if (LoadModel(fileName))
1502 {
1503 addToRecentFiles(fileName, false);
1504 }
1505}
1506
1507void MainWindow::removeFromRecentList(QAction* selectedFileAction)
1508{
1509 if (!selectedFileAction)
1510 {
1511 return;
1512 }
1513
1514 recentFiles.removeAll(selectedFileAction->text());
1515 ui->menuRecent->removeAction(selectedFileAction);
1517}
1518
1519bool MainWindow::CreateFileIfDoesNotExist(const QString& fileName)
1520{
1521 QFileInfo fileInfo(fileName);
1522
1523 if (fileInfo.exists())
1524 {
1525 return true; // File already exists
1526 }
1527
1528 QFile file(fileName);
1529 if (!file.open(QIODevice::WriteOnly))
1530 {
1531 qWarning() << "Failed to create file:" << fileName << "-" << file.errorString();
1532 return false;
1533 }
1534
1535 file.close();
1536 return true;
1537}
1538
1539void MainWindow::addToolBarAction(const QString& text,
1540 const QString& iconPath,
1541 const QString& toolTip,
1542 void (MainWindow::* slot)())
1543{
1544 QAction* action = new QAction(this);
1545 action->setObjectName(text);
1546 action->setText(text);
1547 action->setToolTip(toolTip);
1548
1549 QIcon icon(iconPath);
1550 if (!icon.isNull())
1551 {
1552 action->setIcon(icon);
1553 }
1554 else
1555 {
1556 qWarning() << "Failed to load icon:" << iconPath;
1557 }
1558
1559 ui->toolBar->addAction(action);
1560 connect(action, &QAction::triggered, this, slot);
1561}
1562
1563
1565{
1566 QString iconPath = resourcesPath()+"/Icons/";
1567
1568 // Import Excel
1570 "Import Excel",
1571 iconPath + "Import_Excel.png",
1572 "Import from Excel File",
1574 );
1575
1576 // Save
1578 "Save",
1579 iconPath + "Save.png",
1580 "Save Project",
1582 );
1583
1584 // Open
1586 "Open",
1587 iconPath + "open.png",
1588 "Open Project",
1590 );
1591
1592 // Separator
1593 ui->toolBar->addSeparator();
1594
1595 // Element Properties
1597 "Constituents Selection/Properties",
1598 iconPath + "Element_Props.png",
1599 "Constituents Selection/Properties",
1601 );
1602
1603 // Organic Matter and Size Correction
1605 "Organic Matter and Size Correction",
1606 iconPath + "OMSizeCorrection.png",
1607 "Organic Matter and Size Correction",
1609 );
1610
1611 // Select Samples
1613 "Select Samples",
1614 iconPath + "SelectSamples.png",
1615 "Select Samples",
1617 );
1618}
1619
1621{
1622 if (!plottedData)
1623 {
1624 qWarning() << "No data to display in table";
1625 return;
1626 }
1627
1628 ResultTableViewer* tableViewer = new ResultTableViewer(this);
1629 tableViewer->setWindowFlag(Qt::WindowMinMaxButtonsHint);
1630 tableViewer->setWindowTitle(tr("Elemental Profiles"));
1631
1632 QTableWidget* tableWidget = plottedData->ToTable();
1633 tableViewer->SetTable(tableWidget);
1634
1635 tableViewer->show();
1636}
1637
1639{
1640#ifdef USE_QCHARTS
1641 plotter->ZoomExtents();
1642#else
1644#endif // USE_QCHARTS
1645
1646
1647}
1648
1649
1651{
1652 OptionsDialog dlg(Data()->GetOptions());
1653 dlg.exec();
1654
1655}
1656
1658{
1659#ifdef Q_OS_MAC
1660 // Inside the .app bundle, resources live at Contents/Resources/
1661 // applicationDirPath() returns .../SedSat3.app/Contents/MacOS, so go up one level
1662 return qApp->applicationDirPath() + "/../Resources";
1663#else
1664 // Linux/Windows: resources sit next to the project root, two levels up from build dir
1665 return qApp->applicationDirPath() + "/../../resources";
1666#endif
1667}
Professional about dialog for SedSat3 application.
Definition aboutdialog.h:20
void setVersion(const QString &version)
Sets the application version.
void setBuildDate(const QString &date)
Sets the build date.
TimeSeries< double > EvaluateAsTimeSeries(int numberofpoint=100, const double &stdcoeff=4)
Generate time series representation of the distribution for plotting.
Manages a collection of elemental profiles (samples) for source fingerprinting analysis.
ConcentrationSet * GetElementDistribution(const string &element_name)
Get distribution for a specific element (mutable)
vector< string > GetSampleNames() const
Get all sample names in the set.
Elemental_Profile * AppendProfile(const string &name, const Elemental_Profile &profile=Elemental_Profile(), map< string, element_information > *elementinfo=nullptr)
Add a new sample profile to the set.
Container for elemental concentration data of a single sediment sample.
bool AppendElement(const string &name, double val=0.0)
Add new element to profile.
bool AddScatters(const vector< string > names, const vector< vector< double > > &x, const vector< vector< double > > &y)
bool SetYAxisScaleType(AxisScale axisscale)
bool AddNonUniformScatter(const map< string, vector< double > > &data, int shape_counter=0)
Main application window for SedSAT3 source apportionment analysis.
Definition mainwindow.h:41
void on_actionRecent_triggered()
void addToolBarAction(const QString &text, const QString &iconPath, const QString &toolTip, void(MainWindow::*slot)())
Helper to add an action to the toolbar with icon and connection.
QString resourcesPath() const
void on_old_result_requested(const QModelIndex &index)
bool ReadExcel(const QString &filename)
Reads elemental profile data from Excel file.
QStringList recentFiles
List of recently opened files.
Definition mainwindow.h:270
SourceSinkData * Data()
Returns pointer to the main data collection.
Definition mainwindow.h:73
QMenu * ResultscontextMenu
Context menu for results view.
Definition mainwindow.h:260
void DeleteResults()
GeneralPlotter * plotter
Main plotting widget.
Definition mainwindow.h:249
SourceSinkData dataCollection
Main data collection for analysis.
Definition mainwindow.h:242
void showdistributionsforelements()
void on_show_data_as_table()
QString ProjectFileName
Current project file path.
Definition mainwindow.h:269
void onOpenProject()
QStandardItem * ToQStandardItem(const QString &key, const SourceSinkData *srcsinkdata)
std::unique_ptr< QStandardItemModel > columnViewModel
Model for column/data tree view.
Definition mainwindow.h:256
bool LoadModel(const QString &fileName)
void setupResultsView()
Initializes the results tree view and context menu.
void WriteMessageOnScreen(const QString &text, QColor color=Qt::black)
Writes a message to the application's message display area.
Ui::MainWindow * ui
Definition mainwindow.h:239
bool treeItemChangedProgramatically
Flag for programmatic tree changes.
Definition mainwindow.h:268
void setupToolsView()
Initializes the tools tree view.
bool CreateFileIfDoesNotExist(const QString &fileName)
void Populate_General_ToolBar()
std::unique_ptr< QStandardItemModel > resultsViewModel
Model for results tree view.
Definition mainwindow.h:257
bool saveProjectToFile(const QString &filePath)
Saves project data to the specified file.
QString getSaveFilePath(bool promptUser)
Gets the file path for saving, prompting user if needed.
std::unique_ptr< Conductor > conductor
Analysis command coordinator.
Definition mainwindow.h:275
void on_Options_triggered()
void addToRecentFiles(const QString &fileName, bool addToFile)
QJsonObject buildProjectJson() const
Builds the complete project JSON object.
QJsonDocument formsstructure
Forms structure definitions.
Definition mainwindow.h:264
QString selectedTreeItemType
Currently selected tree item type.
Definition mainwindow.h:267
void on_constituent_properties_triggered()
QStandardItemModel * ToQStandardItemModel(const SourceSinkData *srcsinkdata)
QString TreeQStringSelectedType()
void on_import_excel()
void removeFromRecentList(QAction *selectedFileAction)
void writeRecentFilesList()
void InitiateTables()
void setupConnections()
Sets up all signal-slot connections for UI elements.
MainWindow(QWidget *parent=nullptr)
Constructs the main window.
std::unique_ptr< QMenu > menu
Main menu.
Definition mainwindow.h:273
QJsonObject buildResultsJson() const
Builds the results section of the project JSON.
QJsonDocument loadJson(const QString &fileName)
std::unique_ptr< QWidget > centralform
Central form widget.
Definition mainwindow.h:274
QAction * DeleteAction
Delete action for results.
Definition mainwindow.h:261
std::unique_ptr< Interface > plottedData
Currently plotted data interface.
Definition mainwindow.h:253
void preparetreeviewMenu(const QPoint &pos)
void on_ZoomExtends()
void onOMSizeCorrection()
void onAboutTriggered()
QStandardItem * ElementsToQStandardItem(const QString &key, const SourceSinkData *srcsinkdata)
void readRecentFilesList()
int sinkSheet
Index of sink sheet in Excel import.
Definition mainwindow.h:243
void on_plot_raw_elemental_profiles()
void setupToolBar()
Initializes the general toolbar.
void onCustomContextMenu(const QPoint &pos)
QModelIndex indexresultselected
Currently selected result index.
Definition mainwindow.h:276
void on_tree_selectionChanged(const QItemSelection &changed)
~MainWindow()
Destructor - cleans up UI resources.
bool Execute(const std::string &command, std::map< std::string, std::string > arguments)
Executes a specified analysis command.
void onSaveProject()
void saveJson(const QJsonDocument &document, const QString &fileName)
void onIncludeExcludeSample()
void on_tool_executed(const QModelIndex &index)
void onSaveProjectAs()
GeneralChartPlotter * Plotter()
Definition plotwindow.h:27
Results * result
void SetTable(QTableWidget *tablewidget)
void SetResults(Results *res)
void AppendResult(const ResultItem &resultitem)
QJsonObject toJsonObject()
Definition results.cpp:30
string GetName()
Definition results.h:23
bool ReadFromJson(const QJsonObject &jsonobject)
Definition results.cpp:41
void SetData(SourceSinkData *_data)
void SetMode(mode _mode)
profiles_data ExtractConcentrationData(const vector< vector< string > > &indicators) const
Extract concentration data for specified samples.
void AssignAllDistributions()
Assign distributions to all elements at both dataset and group levels.
QMap< QString, double > * GetOptions()
Retrieves pointer to the options map.
bool ReadFromFile(QFile *fil)
Loads complete dataset from a JSON file.
map< string, vector< double > > ExtractElementDataByGroup(const string &element) const
Extract concentration data for a specific element from all groups.
vector< string > GetElementNames() const
Get all element names in the dataset.
void OutlierAnalysisForAll(const double &lower_threshold=-3, const double &upper_threshold=3)
Performs outlier detection on all source groups.
Elemental_Profile_Set * GetSampleSet(const string &name)
Get a sample set (source or target group) by name.
Elemental_Profile_Set * AppendSampleSet(const string &name, const Elemental_Profile_Set &elemental_profile_set=Elemental_Profile_Set())
Add a new sample set (source or target group) to the dataset.
void Clear()
Clear all data from the object.
Distribution * GetFittedDistribution(const string &element_name)
Get the fitted distribution for a specific element at dataset level.
CMBVector BracketTest(const string &target_sample, bool correct_based_on_om_n_size)
Performs bracket test to check if target concentrations fall within source ranges.
Elemental_Profile_Set ExtractSamplesAsProfileSet(const vector< vector< string > > &indicators) const
Extract samples as an Elemental_Profile_Set.
QJsonObject ElementInformationToJsonObject() const
Exports element information metadata to a JSON object.
QJsonObject OptionsToJsonObject() const
Exports analysis options/settings to a JSON object.
void SetOMandSizeConstituents(const string &_omconstituent, const string &_sizeconsituent)
Sets the names of OM and particle size constituents.
QJsonObject ElementDataToJsonObject() const
Exports all elemental profile data to a JSON object.
void PopulateElementInformation(const map< string, element_information > *ElementInfo=nullptr)
Populate element information metadata.
vector< string > GetGroupNames() const
Get all group names in the dataset.
void PopulateElementDistributions()
Populate element distributions from all groups.
QJsonArray ToolsUsedToJsonObject() const
Exports the list of analysis tools used to a JSON array.
vector< string > OMandSizeConstituents()
Retrieves the names of OM and particle size constituents.
#define RECENT
#define date_compiled
#define max_num_recent_files
QString localAppFolderAddress()
Returns the local application folder address.
#define version
@ elementRole
Definition mainwindow.h:22
@ sampleRole
Definition mainwindow.h:24
@ groupRole
Definition mainwindow.h:23
@ regressions
@ samples
vector< vector< double > > values
vector< string > element_names
vector< string > sample_names