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);
81 conductor->SetWorkingFolder(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
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,
109 connect(
ui->actionRaw_Elemental_Profiles, &QAction::triggered,
113 connect(
ui->actionConstituent_Properties, &QAction::triggered,
115 connect(
ui->actionInclude_Exclude_Samples, &QAction::triggered,
117 connect(
ui->actionOrganic_Matter_Size_correction, &QAction::triggered,
121 connect(
ui->actionAbout, &QAction::triggered,
123 connect(
ui->actionOptions, &QAction::triggered,
127 connect(
ui->ShowTabular, &QPushButton::clicked,
129 connect(
ui->btnZoom, &QPushButton::clicked,
133 connect(
ui->treeViewtools, &QTreeView::doubleClicked,
135 connect(
ui->TreeView_Results, &QTreeView::doubleClicked,
139 connect(
ui->TreeView_Results, &QWidget::customContextMenuRequested,
149 qDebug() <<
"Loading tools from:" << toolsPath;
151 QJsonDocument tools =
loadJson(toolsPath);
153 toolsModel->setHorizontalHeaderLabels(QStringList() <<
"Tools");
155 ui->treeViewtools->setModel(toolsModel);
156 ui->treeViewtools->setEditTriggers(QAbstractItemView::NoEditTriggers);
159 QString formsPath =
resourcesPath() +
"/forms_structures.json";
163 ui->treeView->setSelectionMode(QAbstractItemView::MultiSelection);
164 ui->verticalLayout_3->setContentsMargins(0, 0, 0, 0);
165 ui->verticalLayout_3->setSpacing(0);
168 if (
ui->frame->layout())
170 ui->frame->layout()->setContentsMargins(2, 2, 2, 2);
171 ui->frame->layout()->setSpacing(2);
174 ui->frame->setVisible(
false);
184 ui->TreeView_Results->setContextMenuPolicy(Qt::CustomContextMenu);
196 ui->ShowTabular->setIcon(iconTable);
197 ui->ShowTabular->setEnabled(
false);
201 ui->btnZoom->setIcon(iconZoom);
202 ui->btnZoom->setEnabled(
false);
224 QString fileName = QFileDialog::getSaveFileName(
228 tr(
"CMB Source file (*.cmb);;All files (*.*)"),
230 QFileDialog::DontUseNativeDialog
234 if (fileName.isEmpty())
240 if (!fileName.toLower().endsWith(
".cmb"))
250 QJsonObject resultsJson;
266 if (!resultSetItem || !resultSetItem->
result)
268 qWarning() <<
"Invalid result item at row" << i;
280 QJsonObject projectJson;
284 projectJson[
"Target Group"] = QString::fromStdString(
Data()->GetTargetGroup());
294 QFile file(filePath);
296 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
298 QMessageBox::critical(
301 tr(
"Could not open file for writing:\n%1\n\nError: %2")
303 .arg(file.errorString())
309 QFileInfo fileInfo(file);
310 conductor->SetWorkingFolder(fileInfo.absolutePath());
314 QJsonDocument jsonDocument(projectJson);
316 qint64 bytesWritten = file.write(jsonDocument.toJson());
319 if (bytesWritten == -1)
321 QMessageBox::critical(
324 tr(
"Error writing to file:\n%1\n\nError: %2")
326 .arg(file.errorString())
342 if (filePath.isEmpty())
349 statusBar()->showMessage(tr(
"Project saved successfully"), 3000);
357 if (filePath.isEmpty())
364 statusBar()->showMessage(tr(
"Project saved successfully"), 3000);
370 QString fileName = QFileDialog::getOpenFileName(
374 tr(
"CMB Source file (*.cmb);;All files (*.*)"),
376 QFileDialog::DontUseNativeDialog
379 if (fileName.isEmpty())
387 statusBar()->showMessage(tr(
"Project opened successfully"), 3000);
393 QFile file(fileName);
395 if (!file.open(QIODevice::ReadOnly))
397 QMessageBox::critical(
400 tr(
"Could not open file for reading:\n%1\n\nError: %2")
402 .arg(file.errorString())
408 QByteArray fileData = file.readAll();
411 QJsonDocument jsonDoc = QJsonDocument::fromJson(fileData);
412 if (jsonDoc.isNull())
414 QMessageBox::critical(
417 tr(
"Invalid JSON format in file:\n%1")
423 QJsonObject jsonObject = jsonDoc.object();
426 QFileInfo fileInfo(fileName);
427 conductor->SetWorkingFolder(fileInfo.absolutePath());
430 file.open(QIODevice::ReadOnly);
435 QJsonObject resultsJson = jsonObject[
"Results"].toObject();
439 for (
const QString& key : resultsJson.keys())
442 resultSet->setToolTip(key);
447 for (
auto it = resultSet->
result->begin(); it != resultSet->
result->end(); ++it)
451 QStringList parts = QString::fromStdString(it->first).split(
"OM & Size MLR for ");
452 if (parts.size() > 1)
454 std::string source = parts[1].toStdString();
455 if (
Data()->count(source) > 0)
457 Data()->at(source).SetRegressionModels(
461 Data()->at(source).GetRegressionModels()->begin()->second.GetIndependentVariableNames()
480 ui->ShowTabular->setEnabled(
true);
481 ui->btnZoom->setEnabled(
true);
487 QString fileName = QFileDialog::getOpenFileName(
489 tr(
"Open Excel File"),
491 tr(
"Excel files (*.xlsx);;All files (*.*)"),
493 QFileDialog::DontUseNativeDialog
496 if (fileName.isEmpty())
501 QFileInfo fileInfo(fileName);
502 conductor->SetWorkingFolder(fileInfo.absolutePath());
508 QMessageBox::information(
510 tr(
"Element Selection"),
511 tr(
"To exclude elements from analysis, double-click on an element and uncheck the box."),
526 QList<QStandardItem*> modelItems;
531 QList<QStandardItem*> elementItems;
537 ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
540 connect(
ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
542 connect(
ui->treeView, &QWidget::customContextMenuRequested,
554 Document xlsxR(filename);
557 QMessageBox::critical(
559 tr(
"Excel Load Error"),
560 tr(
"Failed to load Excel file:\n%1").arg(filename)
565 qDebug() <<
"[debug] Successfully loaded xlsx file.";
566 QStringList allSheetNames = xlsxR.sheetNames();
572 QStringList* sheetsToInclude =
nullptr;
573 auto chooseSheetsDlg = std::make_unique<DialogChooseExcelSheets>(
this, &sheetsToInclude);
575 for (
const QString& sheetName : allSheetNames)
577 chooseSheetsDlg->AddItem(sheetName);
578 qDebug() << sheetName;
581 chooseSheetsDlg->exec();
583 if (!sheetsToInclude)
589 QStringList selectedSheets;
590 for (
const QString& sheetName : allSheetNames)
592 if (sheetsToInclude->contains(sheetName))
594 selectedSheets << sheetName;
597 delete sheetsToInclude;
599 if (selectedSheets.isEmpty())
605 auto indicateSheetDlg = std::make_unique<IndicateSheetsDialog>(
this);
606 indicateSheetDlg->Populate_Table(selectedSheets);
607 indicateSheetDlg->exec();
609 qDebug() <<
"Sink Sheet is:" <<
sinkSheet;
612 QList<QStringList> elementNamesBySheet;
614 for (
int sheetIdx = 0; sheetIdx < selectedSheets.count(); ++sheetIdx)
616 xlsxR.selectSheet(selectedSheets[sheetIdx]);
617 QStringList elementNames;
623 auto cell = xlsxR.cellAt(1, col);
627 QString elemName = cell->readValue().toString().trimmed();
628 if (elemName.isEmpty())
631 elementNames.append(elemName);
632 qDebug() << elemName;
637 if (sheetIdx > 0 && elementNames != elementNamesBySheet.last())
639 QMessageBox::warning(
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])
649 QString(
"Element mismatch between sheets '%1' and '%2'")
650 .arg(selectedSheets[sheetIdx - 1])
651 .arg(selectedSheets[sheetIdx]),
657 elementNamesBySheet.append(elementNames);
661 for (
int sheetIdx = 0; sheetIdx < selectedSheets.count(); ++sheetIdx)
663 xlsxR.selectSheet(selectedSheets[sheetIdx]);
666 selectedSheets[sheetIdx].toStdString()
673 auto sampleCell = xlsxR.cellAt(row, 1);
674 if (!sampleCell || sampleCell->readValue().toString().isEmpty())
677 QString sampleName = sampleCell->readValue().toString();
681 for (
int col = 0; col < elementNamesBySheet[0].count(); ++col)
683 auto dataCell = xlsxR.cellAt(row, col + 2);
687 QMessageBox::warning(
690 tr(
"Empty cell found in:\n"
694 .arg(selectedSheets[sheetIdx])
702 bool isNumber =
false;
703 double value = dataCell->readValue().toDouble(&isNumber);
707 QMessageBox::warning(
710 tr(
"Non-numerical value found:\n"
715 .arg(selectedSheets[sheetIdx])
718 .arg(dataCell->readValue().toString())
725 elementNamesBySheet[0][col].toStdString(),
730 profileSet->
AppendProfile(sampleName.toStdString(), elementalProfile);
740 qDebug() <<
"Reading element information done!";
747 ui->textBrowser->setTextColor(color);
748 ui->textBrowser->append(text);
753 QStandardItem* rootItem =
new QStandardItem(key);
754 rootItem->setData(
"RootItem", Qt::UserRole);
758 for (
size_t i = 0; i < groupNames.size(); ++i)
760 QStandardItem* groupItem =
new QStandardItem(QString::fromStdString(groupNames[i]));
761 groupItem->setData(
"Group", Qt::UserRole);
762 groupItem->setData(QString::fromStdString(groupNames[i]),
groupRole);
767 for (
auto it = sampleSet->begin(); it != sampleSet->end(); ++it)
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);
774 groupItem->appendRow(sampleItem);
778 rootItem->appendRow(groupItem);
786 QStandardItem* rootItem =
new QStandardItem(key);
787 rootItem->setData(
"RootItem", Qt::UserRole);
792 for (
size_t elemIdx = 0; elemIdx < elementNames.size(); ++elemIdx)
794 QStandardItem* elementItem =
new QStandardItem(QString::fromStdString(elementNames[elemIdx]));
795 elementItem->setData(
"Element", Qt::UserRole);
796 elementItem->setData(QString::fromStdString(elementNames[elemIdx]),
elementRole);
798 for (
size_t groupIdx = 0; groupIdx < groupNames.size(); ++groupIdx)
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);
805 elementItem->appendRow(groupItem);
808 rootItem->appendRow(elementItem);
822 for (
size_t i = 0; i < groupNames.size(); ++i)
824 QStandardItem* groupItem =
new QStandardItem(QString::fromStdString(groupNames[i]));
825 groupItem->setData(
"Parent", Qt::UserRole);
830 for (
auto it = sampleSet->begin(); it != sampleSet->end(); ++it)
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);
837 groupItem->appendRow(sampleItem);
848 qDebug()<<
"Selection changed "<<changed;
849 ui->ShowTabular->setEnabled(
false);
854 plotter =
new GeneralChartPlotter(
this);
859 ui->verticalLayout_3->addWidget(
plotter);
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]))
874 ui->treeView->selectionModel()->clearSelection();
875 ui->treeView->selectionModel()->select(changed_index,QItemSelectionModel::Select | QItemSelectionModel::Rows);
877 indexes =
ui->treeView->selectionModel()->selectedIndexes();
881 for (
int i=0; i<indexes.size(); i++)
883 qDebug()<<indexes[i].data(Qt::UserRole).toString();
884 if (indexes[i].data(Qt::UserRole).toString()==
"Group")
886 QString Group_Name_Selected = indexes[i].data().toString();
888 for (
int sample_counter=0; sample_counter<Sample_Names.size(); sample_counter++)
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);
896 else if (indexes[i].data(Qt::UserRole).toString()==
"Sample")
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);
903 if (indexes[i].data(Qt::UserRole).toString()==
"Element")
905 qDebug()<<
"Element selected!";
906 PlotType = plottype::element_scatters;
907 QString Element_Name_Selected = indexes[i].data().toString();
909 qDebug()<<
"Data extracted!";
913 plotter =
new GeneralChartPlotter(
this);
917 ui->verticalLayout_3->addWidget(
plotter);
932 if (samples_selected.size()>0 && PlotType==plottype::elemental_profiles)
934 plottedData = std::make_unique<Elemental_Profile_Set>(
937 ui->ShowTabular->setEnabled(
true);
945 ui->frame->setVisible(
true);
946 ui->frame->setEnabled(
true);
947 ui->btnZoom->setEnabled(
true);
952 QFile jsonFile(fileName);
953 jsonFile.open(QFile::ReadOnly);
954 return QJsonDocument().fromJson(jsonFile.readAll());
958 QFile jsonFile(fileName);
959 jsonFile.open(QFile::WriteOnly);
960 jsonFile.write(document.toJson());
965 QStandardItem* standardItem =
new QStandardItem(key);
967 QStringList keys = json.keys();
968 for (
const QString& jsonKey : keys)
970 if (json[jsonKey].isObject())
973 standardItem->appendRow(
ToQStandardItem(jsonKey, json[jsonKey].toObject()));
974 standardItem->setToolTip(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);
992 QStandardItemModel* standardItemModel =
new QStandardItemModel();
993 QJsonObject jsonObject = jsonDocument.object();
996 standardItemModel->appendRow(rootItem);
998 return standardItemModel;
1003 QModelIndexList selectedIndexes =
ui->treeView->selectionModel()->selectedIndexes();
1005 if (!selectedIndexes.isEmpty())
1007 return selectedIndexes[0].data(Qt::UserRole).toString();
1015 QTreeView* tree =
ui->treeView;
1016 QModelIndex index = tree->currentIndex();
1018 if (!index.isValid())
1027 menu = std::make_unique<QMenu>(
this);
1029 if (!index.data(
elementRole).toString().isEmpty())
1031 QAction* showDistributions =
menu->addAction(
"Show fitted distributions");
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";
1039 showDistributions->setData(roleData);
1041 connect(showDistributions, &QAction::triggered,
1047 menu->exec(tree->mapToGlobal(pos));
1053 QAction* action = qobject_cast<QAction*>(sender());
1059 QStringList keysStringList = action->data().toStringList();
1060 QMap<QString, QString> keys;
1062 for (
const QString& keyValue : keysStringList)
1064 QStringList parts = keyValue.split(
"=");
1065 if (parts.size() == 2)
1067 keys[parts[0]] = parts[1];
1071 QString element = keys[
"Element"];
1072 QString group = keys[
"Group"];
1073 QString sample = keys[
"Sample"];
1074 QString actionType = keys[
"Action"];
1076 if (actionType !=
"SFD")
1083 if (group.isEmpty())
1086 TimeSeries<double> elementDist =
1089 plotWindow->
Plotter()->AddTimeSeries(
1091 elementDist.tToStdVector(),
1092 elementDist.ValuesToStdVector()
1100 ->GetFittedDistribution()
1101 ->EvaluateAsTimeSeries();
1103 plotWindow->
Plotter()->AddTimeSeries(
1105 elementDist.tToStdVector(),
1106 elementDist.ValuesToStdVector()
1113 TimeSeries<double> elementDist =
1115 ->GetElementDistribution(element.toStdString())
1116 ->GetFittedDistribution()
1117 ->EvaluateAsTimeSeries();
1119 plotWindow->
Plotter()->AddTimeSeries(
1120 (group +
":" + element).toStdString(),
1121 elementDist.tToStdVector(),
1122 elementDist.ValuesToStdVector()
1126 plotWindow->setWindowTitle(element);
1127 plotWindow->
Plotter()->SetLegend(
true);
1135 formElems->
table()->setModel(elementTableModel);
1138 formElems->
table()->setItemDelegate(elemDelegate);
1140 ui->verticalLayout_middle->addWidget(formElems);
1146 double outlierThreshold =
Data()->
GetOptions()->operator[](
"Outlier deviation threshold");
1149 if (outlierThreshold == 3)
1163 ui->verticalLayout_middle->addWidget(sampleSelector);
1173 QMessageBox::warning(
1176 tr(
"Organic Matter and Size Correction must be performed first."),
1187 ui->verticalLayout_middle->addWidget(sampleSelector);
1194 qDebug() <<
"Tool executed" << index.data() <<
":" << index.data(Qt::UserRole);
1199 QMessageBox::warning(
1202 tr(
"No data has been loaded."),
1209 QString toolName = index.data(Qt::UserRole).toString();
1211 if (mainJsonObject.contains(toolName))
1213 qDebug() << toolName;
1215 QJsonObject formObject = mainJsonObject.value(toolName).toObject();
1217 centralform = std::make_unique<GenericForm>(&formObject,
this,
this);
1237 if (!resultSetItem || !resultSetItem->
result)
1239 qWarning() <<
"Invalid result item at row" << index.row();
1247 resultsWindow->setWindowTitle(resultSetItem->text());
1249 for (
auto it = resultSet->begin(); it != resultSet->end(); ++it)
1254 resultsWindow->show();
1261 bool outcome =
conductor->Execute(command, arguments);
1272 QString timestamp = QDateTime::currentDateTime().toString(Qt::TextDate);
1273 QString resultName = QString::fromStdString(resultsForStorage->
GetName()) +
"_" + timestamp;
1277 resultSetItem->setToolTip(resultName);
1278 resultSetItem->
result = resultsForStorage;
1284 resultsWindow->setWindowTitle(resultName);
1287 for (
auto it = resultsForStorage->begin(); it != resultsForStorage->end(); ++it)
1292 resultsWindow->show();
1318 qWarning() <<
"Attempted to delete with invalid index";
1325 int ret = QMessageBox::question(
1327 tr(
"Delete Result"),
1328 tr(
"Are you sure you want to delete '%1'?").arg(item->text()),
1329 QMessageBox::Yes | QMessageBox::No,
1333 if (ret == QMessageBox::Yes)
1342 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1345 QFile file(recentFilePath);
1346 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1348 qDebug() <<
"No recent files list found";
1356 const QByteArray contents = file.readAll();
1357 const bool readFailed = (file.error() != QFileDevice::NoError);
1358 const QString readError = file.errorString();
1363 qWarning() <<
"Failed to read recent files list:" << readError;
1367 QStringList allLines;
1368 const QStringList rawLines = QString::fromUtf8(contents).split(
'\n', Qt::SkipEmptyParts);
1370 for (
const QString& rawLine : rawLines)
1372 const QString line = rawLine.trimmed();
1373 if (!line.isEmpty())
1375 allLines.append(line);
1381 for (
int i = startIndex; i < allLines.size(); ++i)
1389 if (fileName.trimmed().isEmpty())
1394 QString trimmedFileName = fileName.trimmed();
1395 bool rewriteFile =
false;
1400 int existingIndex =
recentFiles.indexOf(trimmedFileName);
1406 QList<QAction*> actions =
ui->menuRecent->actions();
1407 int actionIndex =
recentFiles.size() - 1 - existingIndex;
1408 if (actionIndex >= 0 && actionIndex < actions.size())
1410 ui->menuRecent->removeAction(actions[actionIndex]);
1431 QList<QAction*> actions =
ui->menuRecent->actions();
1432 if (!actions.isEmpty())
1434 ui->menuRecent->removeAction(actions.last());
1444 QAction* fileAction =
new QAction(trimmedFileName,
this);
1446 QList<QAction*> actions =
ui->menuRecent->actions();
1447 if (!actions.isEmpty())
1449 ui->menuRecent->insertAction(actions.first(), fileAction);
1453 ui->menuRecent->addAction(fileAction);
1461 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1465 QFile file(recentFilePath);
1466 if (file.open(QIODevice::Append | QIODevice::Text))
1468 QTextStream out(&file);
1469 out << trimmedFileName <<
"\n";
1482 QString recentFilePath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
1485 QFile file(recentFilePath);
1486 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
1488 qWarning() <<
"Failed to write recent files list:" << file.errorString();
1492 QTextStream out(&file);
1495 out << fileName <<
"\n";
1503 QString localAppDataPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
1504 qDebug() <<
"Local AppData Folder:" << localAppDataPath;
1505 return localAppDataPath;
1510 QAction* action = qobject_cast<QAction*>(sender());
1513 qWarning() <<
"Invalid sender in on_actionRecent_triggered";
1517 QString fileName = action->text();
1526 if (!selectedFileAction)
1531 recentFiles.removeAll(selectedFileAction->text());
1532 ui->menuRecent->removeAction(selectedFileAction);
1538 QFileInfo fileInfo(fileName);
1540 if (fileInfo.exists())
1545 QFile file(fileName);
1546 if (!file.open(QIODevice::WriteOnly))
1548 qWarning() <<
"Failed to create file:" << fileName <<
"-" << file.errorString();
1557 const QString& iconPath,
1558 const QString& toolTip,
1561 QAction* action =
new QAction(
this);
1562 action->setObjectName(text);
1563 action->setText(text);
1564 action->setToolTip(toolTip);
1566 QIcon icon(iconPath);
1569 action->setIcon(icon);
1573 qWarning() <<
"Failed to load icon:" << iconPath;
1576 ui->toolBar->addAction(action);
1577 connect(action, &QAction::triggered,
this, slot);
1588 iconPath +
"Import_Excel.png",
1589 "Import from Excel File",
1596 iconPath +
"Save.png",
1604 iconPath +
"open.png",
1610 ui->toolBar->addSeparator();
1614 "Constituents Selection/Properties",
1615 iconPath +
"Element_Props.png",
1616 "Constituents Selection/Properties",
1622 "Organic Matter and Size Correction",
1623 iconPath +
"OMSizeCorrection.png",
1624 "Organic Matter and Size Correction",
1631 iconPath +
"SelectSamples.png",
1641 qWarning() <<
"No data to display in table";
1646 tableViewer->setWindowFlag(Qt::WindowMinMaxButtonsHint);
1647 tableViewer->setWindowTitle(tr(
"Elemental Profiles"));
1649 QTableWidget* tableWidget =
plottedData->ToTable();
1650 tableViewer->
SetTable(tableWidget);
1652 tableViewer->show();
1679 return qApp->applicationDirPath() +
"/../Resources";
1682 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()
std::unique_ptr< GuiHost > analysis_host
Reports analyses through this window.
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