2#include "ui_mainwindow.h"
8#include "xlsxdocument.h"
9#include "xlsxchartsheet.h"
10#include "xlsxcellrange.h"
12#include "xlsxrichstring.h"
13#include "xlsxworkbook.h"
19#include "QJsonDocument"
35#include <QStandardPaths>
45#define RECENT "SedSatrecentFiles.txt"
47#ifndef max_num_recent_files
48#define max_num_recent_files 15
52#define version "1.1.6"
53#define date_compiled "12/20/2025"
58 : QMainWindow(parent),
62 columnViewModel(nullptr),
63 resultsViewModel(nullptr),
64 ResultscontextMenu(nullptr),
65 DeleteAction(nullptr),
66 selectedTreeItemType(
"None"),
67 treeItemChangedProgramatically(false),
74 setWindowIcon(mainIcon);
77 conductor = std::make_unique<Conductor>(
this);
78 conductor->SetWorkingFolder(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
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,
106 connect(
ui->actionRaw_Elemental_Profiles, &QAction::triggered,
110 connect(
ui->actionConstituent_Properties, &QAction::triggered,
112 connect(
ui->actionInclude_Exclude_Samples, &QAction::triggered,
114 connect(
ui->actionOrganic_Matter_Size_correction, &QAction::triggered,
118 connect(
ui->actionAbout, &QAction::triggered,
120 connect(
ui->actionOptions, &QAction::triggered,
124 connect(
ui->ShowTabular, &QPushButton::clicked,
126 connect(
ui->btnZoom, &QPushButton::clicked,
130 connect(
ui->treeViewtools, &QTreeView::doubleClicked,
132 connect(
ui->TreeView_Results, &QTreeView::doubleClicked,
136 connect(
ui->TreeView_Results, &QWidget::customContextMenuRequested,
146 qDebug() <<
"Loading tools from:" << toolsPath;
148 QJsonDocument tools =
loadJson(toolsPath);
150 toolsModel->setHorizontalHeaderLabels(QStringList() <<
"Tools");
152 ui->treeViewtools->setModel(toolsModel);
153 ui->treeViewtools->setEditTriggers(QAbstractItemView::NoEditTriggers);
156 QString formsPath =
resourcesPath() +
"/forms_structures.json";
160 ui->treeView->setSelectionMode(QAbstractItemView::MultiSelection);
161 ui->verticalLayout_3->setContentsMargins(0, 0, 0, 0);
162 ui->verticalLayout_3->setSpacing(0);
165 if (
ui->frame->layout())
167 ui->frame->layout()->setContentsMargins(2, 2, 2, 2);
168 ui->frame->layout()->setSpacing(2);
171 ui->frame->setVisible(
false);
181 ui->TreeView_Results->setContextMenuPolicy(Qt::CustomContextMenu);
193 ui->ShowTabular->setIcon(iconTable);
194 ui->ShowTabular->setEnabled(
false);
198 ui->btnZoom->setIcon(iconZoom);
199 ui->btnZoom->setEnabled(
false);
221 QString fileName = QFileDialog::getSaveFileName(
225 tr(
"CMB Source file (*.cmb);;All files (*.*)"),
227 QFileDialog::DontUseNativeDialog
231 if (fileName.isEmpty())
237 if (!fileName.toLower().endsWith(
".cmb"))
247 QJsonObject resultsJson;
263 if (!resultSetItem || !resultSetItem->
result)
265 qWarning() <<
"Invalid result item at row" << i;
277 QJsonObject projectJson;
281 projectJson[
"Target Group"] = QString::fromStdString(
Data()->GetTargetGroup());
291 QFile file(filePath);
293 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
295 QMessageBox::critical(
298 tr(
"Could not open file for writing:\n%1\n\nError: %2")
300 .arg(file.errorString())
306 QFileInfo fileInfo(file);
307 conductor->SetWorkingFolder(fileInfo.absolutePath());
311 QJsonDocument jsonDocument(projectJson);
313 qint64 bytesWritten = file.write(jsonDocument.toJson());
316 if (bytesWritten == -1)
318 QMessageBox::critical(
321 tr(
"Error writing to file:\n%1\n\nError: %2")
323 .arg(file.errorString())
339 if (filePath.isEmpty())
346 statusBar()->showMessage(tr(
"Project saved successfully"), 3000);
354 if (filePath.isEmpty())
361 statusBar()->showMessage(tr(
"Project saved successfully"), 3000);
367 QString fileName = QFileDialog::getOpenFileName(
371 tr(
"CMB Source file (*.cmb);;All files (*.*)"),
373 QFileDialog::DontUseNativeDialog
376 if (fileName.isEmpty())
384 statusBar()->showMessage(tr(
"Project opened successfully"), 3000);
390 QFile file(fileName);
392 if (!file.open(QIODevice::ReadOnly))
394 QMessageBox::critical(
397 tr(
"Could not open file for reading:\n%1\n\nError: %2")
399 .arg(file.errorString())
405 QByteArray fileData = file.readAll();
408 QJsonDocument jsonDoc = QJsonDocument::fromJson(fileData);
409 if (jsonDoc.isNull())
411 QMessageBox::critical(
414 tr(
"Invalid JSON format in file:\n%1")
420 QJsonObject jsonObject = jsonDoc.object();
423 QFileInfo fileInfo(fileName);
424 conductor->SetWorkingFolder(fileInfo.absolutePath());
427 file.open(QIODevice::ReadOnly);
432 QJsonObject resultsJson = jsonObject[
"Results"].toObject();
436 for (
const QString& key : resultsJson.keys())
439 resultSet->setToolTip(key);
444 for (
auto it = resultSet->
result->begin(); it != resultSet->
result->end(); ++it)
448 QStringList parts = QString::fromStdString(it->first).split(
"OM & Size MLR for ");
449 if (parts.size() > 1)
451 std::string source = parts[1].toStdString();
452 if (
Data()->count(source) > 0)
454 Data()->at(source).SetRegressionModels(
458 Data()->at(source).GetRegressionModels()->begin()->second.GetIndependentVariableNames()
477 ui->ShowTabular->setEnabled(
true);
478 ui->btnZoom->setEnabled(
true);
484 QString fileName = QFileDialog::getOpenFileName(
486 tr(
"Open Excel File"),
488 tr(
"Excel files (*.xlsx);;All files (*.*)"),
490 QFileDialog::DontUseNativeDialog
493 if (fileName.isEmpty())
498 QFileInfo fileInfo(fileName);
499 conductor->SetWorkingFolder(fileInfo.absolutePath());
505 QMessageBox::information(
507 tr(
"Element Selection"),
508 tr(
"To exclude elements from analysis, double-click on an element and uncheck the box."),
523 QList<QStandardItem*> modelItems;
528 QList<QStandardItem*> elementItems;
534 ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
537 connect(
ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
539 connect(
ui->treeView, &QWidget::customContextMenuRequested,
551 Document xlsxR(filename);
554 QMessageBox::critical(
556 tr(
"Excel Load Error"),
557 tr(
"Failed to load Excel file:\n%1").arg(filename)
562 qDebug() <<
"[debug] Successfully loaded xlsx file.";
563 QStringList allSheetNames = xlsxR.sheetNames();
569 QStringList* sheetsToInclude =
nullptr;
570 auto chooseSheetsDlg = std::make_unique<DialogChooseExcelSheets>(
this, &sheetsToInclude);
572 for (
const QString& sheetName : allSheetNames)
574 chooseSheetsDlg->AddItem(sheetName);
575 qDebug() << sheetName;
578 chooseSheetsDlg->exec();
580 if (!sheetsToInclude)
586 QStringList selectedSheets;
587 for (
const QString& sheetName : allSheetNames)
589 if (sheetsToInclude->contains(sheetName))
591 selectedSheets << sheetName;
594 delete sheetsToInclude;
596 if (selectedSheets.isEmpty())
602 auto indicateSheetDlg = std::make_unique<IndicateSheetsDialog>(
this);
603 indicateSheetDlg->Populate_Table(selectedSheets);
604 indicateSheetDlg->exec();
606 qDebug() <<
"Sink Sheet is:" <<
sinkSheet;
609 QList<QStringList> elementNamesBySheet;
611 for (
int sheetIdx = 0; sheetIdx < selectedSheets.count(); ++sheetIdx)
613 xlsxR.selectSheet(selectedSheets[sheetIdx]);
614 QStringList elementNames;
620 auto cell = xlsxR.cellAt(1, col);
624 QString elemName = cell->readValue().toString().trimmed();
625 if (elemName.isEmpty())
628 elementNames.append(elemName);
629 qDebug() << elemName;
634 if (sheetIdx > 0 && elementNames != elementNamesBySheet.last())
636 QMessageBox::warning(
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])
646 QString(
"Element mismatch between sheets '%1' and '%2'")
647 .arg(selectedSheets[sheetIdx - 1])
648 .arg(selectedSheets[sheetIdx]),
654 elementNamesBySheet.append(elementNames);
658 for (
int sheetIdx = 0; sheetIdx < selectedSheets.count(); ++sheetIdx)
660 xlsxR.selectSheet(selectedSheets[sheetIdx]);
663 selectedSheets[sheetIdx].toStdString()
670 auto sampleCell = xlsxR.cellAt(row, 1);
671 if (!sampleCell || sampleCell->readValue().toString().isEmpty())
674 QString sampleName = sampleCell->readValue().toString();
678 for (
int col = 0; col < elementNamesBySheet[0].count(); ++col)
680 auto dataCell = xlsxR.cellAt(row, col + 2);
684 QMessageBox::warning(
687 tr(
"Empty cell found in:\n"
691 .arg(selectedSheets[sheetIdx])
699 bool isNumber =
false;
700 double value = dataCell->readValue().toDouble(&isNumber);
704 QMessageBox::warning(
707 tr(
"Non-numerical value found:\n"
712 .arg(selectedSheets[sheetIdx])
715 .arg(dataCell->readValue().toString())
722 elementNamesBySheet[0][col].toStdString(),
727 profileSet->
AppendProfile(sampleName.toStdString(), elementalProfile);
737 qDebug() <<
"Reading element information done!";
744 ui->textBrowser->setTextColor(color);
745 ui->textBrowser->append(text);
750 QStandardItem* rootItem =
new QStandardItem(key);
751 rootItem->setData(
"RootItem", Qt::UserRole);
755 for (
size_t i = 0; i < groupNames.size(); ++i)
757 QStandardItem* groupItem =
new QStandardItem(QString::fromStdString(groupNames[i]));
758 groupItem->setData(
"Group", Qt::UserRole);
759 groupItem->setData(QString::fromStdString(groupNames[i]),
groupRole);
764 for (
auto it = sampleSet->begin(); it != sampleSet->end(); ++it)
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);
771 groupItem->appendRow(sampleItem);
775 rootItem->appendRow(groupItem);
783 QStandardItem* rootItem =
new QStandardItem(key);
784 rootItem->setData(
"RootItem", Qt::UserRole);
789 for (
size_t elemIdx = 0; elemIdx < elementNames.size(); ++elemIdx)
791 QStandardItem* elementItem =
new QStandardItem(QString::fromStdString(elementNames[elemIdx]));
792 elementItem->setData(
"Element", Qt::UserRole);
793 elementItem->setData(QString::fromStdString(elementNames[elemIdx]),
elementRole);
795 for (
size_t groupIdx = 0; groupIdx < groupNames.size(); ++groupIdx)
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);
802 elementItem->appendRow(groupItem);
805 rootItem->appendRow(elementItem);
819 for (
size_t i = 0; i < groupNames.size(); ++i)
821 QStandardItem* groupItem =
new QStandardItem(QString::fromStdString(groupNames[i]));
822 groupItem->setData(
"Parent", Qt::UserRole);
827 for (
auto it = sampleSet->begin(); it != sampleSet->end(); ++it)
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);
834 groupItem->appendRow(sampleItem);
845 qDebug()<<
"Selection changed "<<changed;
846 ui->ShowTabular->setEnabled(
false);
851 plotter =
new GeneralChartPlotter(
this);
856 ui->verticalLayout_3->addWidget(
plotter);
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]))
871 ui->treeView->selectionModel()->clearSelection();
872 ui->treeView->selectionModel()->select(changed_index,QItemSelectionModel::Select | QItemSelectionModel::Rows);
874 indexes =
ui->treeView->selectionModel()->selectedIndexes();
878 for (
int i=0; i<indexes.size(); i++)
880 qDebug()<<indexes[i].data(Qt::UserRole).toString();
881 if (indexes[i].data(Qt::UserRole).toString()==
"Group")
883 QString Group_Name_Selected = indexes[i].data().toString();
885 for (
int sample_counter=0; sample_counter<Sample_Names.size(); sample_counter++)
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);
893 else if (indexes[i].data(Qt::UserRole).toString()==
"Sample")
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);
900 if (indexes[i].data(Qt::UserRole).toString()==
"Element")
902 qDebug()<<
"Element selected!";
903 PlotType = plottype::element_scatters;
904 QString Element_Name_Selected = indexes[i].data().toString();
906 qDebug()<<
"Data extracted!";
910 plotter =
new GeneralChartPlotter(
this);
914 ui->verticalLayout_3->addWidget(
plotter);
929 if (samples_selected.size()>0 && PlotType==plottype::elemental_profiles)
931 plottedData = std::make_unique<Elemental_Profile_Set>(
934 ui->ShowTabular->setEnabled(
true);
942 ui->frame->setVisible(
true);
943 ui->frame->setEnabled(
true);
944 ui->btnZoom->setEnabled(
true);
949 QFile jsonFile(fileName);
950 jsonFile.open(QFile::ReadOnly);
951 return QJsonDocument().fromJson(jsonFile.readAll());
955 QFile jsonFile(fileName);
956 jsonFile.open(QFile::WriteOnly);
957 jsonFile.write(document.toJson());
962 QStandardItem* standardItem =
new QStandardItem(key);
964 QStringList keys = json.keys();
965 for (
const QString& jsonKey : keys)
967 if (json[jsonKey].isObject())
970 standardItem->appendRow(
ToQStandardItem(jsonKey, json[jsonKey].toObject()));
971 standardItem->setToolTip(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);
989 QStandardItemModel* standardItemModel =
new QStandardItemModel();
990 QJsonObject jsonObject = jsonDocument.object();
993 standardItemModel->appendRow(rootItem);
995 return standardItemModel;
1000 QModelIndexList selectedIndexes =
ui->treeView->selectionModel()->selectedIndexes();
1002 if (!selectedIndexes.isEmpty())
1004 return selectedIndexes[0].data(Qt::UserRole).toString();
1012 QTreeView* tree =
ui->treeView;
1013 QModelIndex index = tree->currentIndex();
1015 if (!index.isValid())
1024 menu = std::make_unique<QMenu>(
this);
1026 if (!index.data(
elementRole).toString().isEmpty())
1028 QAction* showDistributions =
menu->addAction(
"Show fitted distributions");
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";
1036 showDistributions->setData(roleData);
1038 connect(showDistributions, &QAction::triggered,
1044 menu->exec(tree->mapToGlobal(pos));
1050 QAction* action = qobject_cast<QAction*>(sender());
1056 QStringList keysStringList = action->data().toStringList();
1057 QMap<QString, QString> keys;
1059 for (
const QString& keyValue : keysStringList)
1061 QStringList parts = keyValue.split(
"=");
1062 if (parts.size() == 2)
1064 keys[parts[0]] = parts[1];
1068 QString element = keys[
"Element"];
1069 QString group = keys[
"Group"];
1070 QString sample = keys[
"Sample"];
1071 QString actionType = keys[
"Action"];
1073 if (actionType !=
"SFD")
1080 if (group.isEmpty())
1083 TimeSeries<double> elementDist =
1086 plotWindow->
Plotter()->AddTimeSeries(
1088 elementDist.tToStdVector(),
1089 elementDist.ValuesToStdVector()
1097 ->GetFittedDistribution()
1098 ->EvaluateAsTimeSeries();
1100 plotWindow->
Plotter()->AddTimeSeries(
1102 elementDist.tToStdVector(),
1103 elementDist.ValuesToStdVector()
1110 TimeSeries<double> elementDist =
1112 ->GetElementDistribution(element.toStdString())
1113 ->GetFittedDistribution()
1114 ->EvaluateAsTimeSeries();
1116 plotWindow->
Plotter()->AddTimeSeries(
1117 (group +
":" + element).toStdString(),
1118 elementDist.tToStdVector(),
1119 elementDist.ValuesToStdVector()
1123 plotWindow->setWindowTitle(element);
1124 plotWindow->
Plotter()->SetLegend(
true);
1132 formElems->
table()->setModel(elementTableModel);
1135 formElems->
table()->setItemDelegate(elemDelegate);
1137 ui->verticalLayout_middle->addWidget(formElems);
1143 double outlierThreshold =
Data()->
GetOptions()->operator[](
"Outlier deviation threshold");
1146 if (outlierThreshold == 3)
1160 ui->verticalLayout_middle->addWidget(sampleSelector);
1170 QMessageBox::warning(
1173 tr(
"Organic Matter and Size Correction must be performed first."),
1184 ui->verticalLayout_middle->addWidget(sampleSelector);
1191 qDebug() <<
"Tool executed" << index.data() <<
":" << index.data(Qt::UserRole);
1196 QMessageBox::warning(
1199 tr(
"No data has been loaded."),
1206 QString toolName = index.data(Qt::UserRole).toString();
1208 if (mainJsonObject.contains(toolName))
1210 qDebug() << toolName;
1212 QJsonObject formObject = mainJsonObject.value(toolName).toObject();
1214 centralform = std::make_unique<GenericForm>(&formObject,
this,
this);
1234 if (!resultSetItem || !resultSetItem->
result)
1236 qWarning() <<
"Invalid result item at row" << index.row();
1244 resultsWindow->setWindowTitle(resultSetItem->text());
1246 for (
auto it = resultSet->begin(); it != resultSet->end(); ++it)
1251 resultsWindow->show();
1258 bool outcome =
conductor->Execute(command, arguments);
1269 QString timestamp = QDateTime::currentDateTime().toString(Qt::TextDate);
1270 QString resultName = QString::fromStdString(resultsForStorage->
GetName()) +
"_" + timestamp;
1274 resultSetItem->setToolTip(resultName);
1275 resultSetItem->
result = resultsForStorage;
1281 resultsWindow->setWindowTitle(resultName);
1284 for (
auto it = resultsForStorage->begin(); it != resultsForStorage->end(); ++it)
1289 resultsWindow->show();
1315 qWarning() <<
"Attempted to delete with invalid index";
1322 int ret = QMessageBox::question(
1324 tr(
"Delete Result"),
1325 tr(
"Are you sure you want to delete '%1'?").arg(item->text()),
1326 QMessageBox::Yes | QMessageBox::No,
1330 if (ret == QMessageBox::Yes)
1339 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1342 QFile file(recentFilePath);
1343 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1345 qDebug() <<
"No recent files list found";
1349 QTextStream in(&file);
1350 QStringList allLines;
1354 QString line = in.readLine().trimmed();
1355 if (!line.isEmpty())
1357 allLines.append(line);
1364 for (
int i = startIndex; i < allLines.size(); ++i)
1372 if (fileName.trimmed().isEmpty())
1377 QString trimmedFileName = fileName.trimmed();
1378 bool rewriteFile =
false;
1383 int existingIndex =
recentFiles.indexOf(trimmedFileName);
1389 QList<QAction*> actions =
ui->menuRecent->actions();
1390 int actionIndex =
recentFiles.size() - 1 - existingIndex;
1391 if (actionIndex >= 0 && actionIndex < actions.size())
1393 ui->menuRecent->removeAction(actions[actionIndex]);
1414 QList<QAction*> actions =
ui->menuRecent->actions();
1415 if (!actions.isEmpty())
1417 ui->menuRecent->removeAction(actions.last());
1427 QAction* fileAction =
new QAction(trimmedFileName,
this);
1429 QList<QAction*> actions =
ui->menuRecent->actions();
1430 if (!actions.isEmpty())
1432 ui->menuRecent->insertAction(actions.first(), fileAction);
1436 ui->menuRecent->addAction(fileAction);
1444 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1448 QFile file(recentFilePath);
1449 if (file.open(QIODevice::Append | QIODevice::Text))
1451 QTextStream out(&file);
1452 out << trimmedFileName <<
"\n";
1465 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1468 QFile file(recentFilePath);
1469 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
1471 qWarning() <<
"Failed to write recent files list:" << file.errorString();
1475 QTextStream out(&file);
1478 out << fileName <<
"\n";
1486 QString localAppDataPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
1487 qDebug() <<
"Local AppData Folder:" << localAppDataPath;
1488 return localAppDataPath;
1493 QAction* action = qobject_cast<QAction*>(sender());
1496 qWarning() <<
"Invalid sender in on_actionRecent_triggered";
1500 QString fileName = action->text();
1509 if (!selectedFileAction)
1514 recentFiles.removeAll(selectedFileAction->text());
1515 ui->menuRecent->removeAction(selectedFileAction);
1521 QFileInfo fileInfo(fileName);
1523 if (fileInfo.exists())
1528 QFile file(fileName);
1529 if (!file.open(QIODevice::WriteOnly))
1531 qWarning() <<
"Failed to create file:" << fileName <<
"-" << file.errorString();
1540 const QString& iconPath,
1541 const QString& toolTip,
1544 QAction* action =
new QAction(
this);
1545 action->setObjectName(text);
1546 action->setText(text);
1547 action->setToolTip(toolTip);
1549 QIcon icon(iconPath);
1552 action->setIcon(icon);
1556 qWarning() <<
"Failed to load icon:" << iconPath;
1559 ui->toolBar->addAction(action);
1560 connect(action, &QAction::triggered,
this, slot);
1571 iconPath +
"Import_Excel.png",
1572 "Import from Excel File",
1579 iconPath +
"Save.png",
1587 iconPath +
"open.png",
1593 ui->toolBar->addSeparator();
1597 "Constituents Selection/Properties",
1598 iconPath +
"Element_Props.png",
1599 "Constituents Selection/Properties",
1605 "Organic Matter and Size Correction",
1606 iconPath +
"OMSizeCorrection.png",
1607 "Organic Matter and Size Correction",
1614 iconPath +
"SelectSamples.png",
1624 qWarning() <<
"No data to display in table";
1629 tableViewer->setWindowFlag(Qt::WindowMinMaxButtonsHint);
1630 tableViewer->setWindowTitle(tr(
"Elemental Profiles"));
1632 QTableWidget* tableWidget =
plottedData->ToTable();
1633 tableViewer->
SetTable(tableWidget);
1635 tableViewer->show();
1662 return qApp->applicationDirPath() +
"/../Resources";
1665 return qApp->applicationDirPath() +
"/../../resources";
Professional about dialog for SedSat3 application.
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.
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.
SourceSinkData * Data()
Returns pointer to the main data collection.
QMenu * ResultscontextMenu
Context menu for results view.
GeneralPlotter * plotter
Main plotting widget.
SourceSinkData dataCollection
Main data collection for analysis.
void showdistributionsforelements()
void on_show_data_as_table()
QString ProjectFileName
Current project file path.
QStandardItem * ToQStandardItem(const QString &key, const SourceSinkData *srcsinkdata)
std::unique_ptr< QStandardItemModel > columnViewModel
Model for column/data tree view.
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.
bool treeItemChangedProgramatically
Flag for programmatic tree changes.
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.
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.
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.
QString selectedTreeItemType
Currently selected tree item type.
void on_constituent_properties_triggered()
QStandardItemModel * ToQStandardItemModel(const SourceSinkData *srcsinkdata)
QString TreeQStringSelectedType()
void removeFromRecentList(QAction *selectedFileAction)
void writeRecentFilesList()
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.
QJsonObject buildResultsJson() const
Builds the results section of the project JSON.
QJsonDocument loadJson(const QString &fileName)
std::unique_ptr< QWidget > centralform
Central form widget.
QAction * DeleteAction
Delete action for results.
std::unique_ptr< Interface > plottedData
Currently plotted data interface.
void preparetreeviewMenu(const QPoint &pos)
void onOMSizeCorrection()
QStandardItem * ElementsToQStandardItem(const QString &key, const SourceSinkData *srcsinkdata)
void readRecentFilesList()
int sinkSheet
Index of sink sheet in Excel import.
void on_plot_raw_elemental_profiles()
void setupToolBar()
Initializes the general toolbar.
void onCustomContextMenu(const QPoint &pos)
QModelIndex indexresultselected
Currently selected result index.
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 saveJson(const QJsonDocument &document, const QString &fileName)
void onIncludeExcludeSample()
void on_tool_executed(const QModelIndex &index)
GeneralChartPlotter * Plotter()
void SetTable(QTableWidget *tablewidget)
void SetResults(Results *res)
void AppendResult(const ResultItem &resultitem)
QJsonObject toJsonObject()
bool ReadFromJson(const QJsonObject &jsonobject)
void SetData(SourceSinkData *_data)
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 max_num_recent_files
QString localAppFolderAddress()
Returns the local application folder address.
vector< vector< double > > values
vector< string > element_names
vector< string > sample_names