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