diff --git a/.gitignore b/.gitignore index d58186a..1395642 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ # Sensitive Information OpenSRAUserPass.h -*.pro.user \ No newline at end of file +*.pro.user + +# CMake / Conan build output (Qt6/QGIS4 migration) +build/ +CMakeUserPresets.json +*.log \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..48c6fc6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,246 @@ +cmake_minimum_required(VERSION 3.21) + +project(OpenSRA VERSION 1.0.0 LANGUAGES CXX C) + +# ---- C++20 (mirror R2D; SimCenterCommon helper modules compile as C++20) ---- +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(MSVC) + # Force-include msvc_fix.h before any Windows header (byte/RPC clash + lean+mean/nominmax) + file(TO_NATIVE_PATH "${CMAKE_SOURCE_DIR}/msvc_fix.h" MSVC_FIX_PATH) + add_compile_options("/FI${MSVC_FIX_PATH}") + add_compile_definitions(WIN32_LEAN_AND_MEAN) + add_compile_definitions(NOMINMAX) +endif() + +# ---- QGIS-fork dependency roots (sibling layout) appended to CMAKE_PREFIX_PATH ---- +# QCA + Qt6Keychain + Qwt + QScintilla come from the SimCenter deps tree (QGIS/DEPS); +# gdal/geos/proj headers come from the QGIS fork's vcpkg tree. Appended (not prepended) +# so conan-provided packages keep priority. +set(PATH_TO_QGIS "${CMAKE_CURRENT_LIST_DIR}/../QGIS") +list(APPEND CMAKE_PREFIX_PATH + "${PATH_TO_QGIS}/DEPS" + "${PATH_TO_QGIS}/build/vcpkg_installed/x64-windows") + +# ---- Qt ---- +# Mirrors R2D's component set, minus SerialPort (OpenSRA's compiled sources do not use +# QtSerialPort; only the QGIS fork does, which is linked, not compiled, here). +find_package(Qt6 REQUIRED COMPONENTS + Core Gui Widgets + Charts Concurrent Network Sql Xml WebEngineWidgets + 3DCore 3DRender 3DExtras + OpenGL OpenGLWidgets Svg + Positioning QuickWidgets +) + +# Qt6Keychain resolves from the SimCenter deps tree (QGIS/DEPS/lib/cmake/Qt6Keychain), +# now on CMAKE_PREFIX_PATH. REQUIRED for the link (Session 4). +find_package(Qt6Keychain REQUIRED) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +# ---- Paths (single-".." sibling layout: OpenSRA_GUI/{OpenSRAFrontEnd,R2DTool,SimCenterCommon,QGIS}) ---- +set(PATH_TO_COMMON "${CMAKE_CURRENT_LIST_DIR}/../SimCenterCommon") +set(PATH_TO_R2D "${CMAKE_CURRENT_LIST_DIR}/../R2DTool") +# NOTE: no PATH_TO_QGIS_DEPS. SimCenterQGIS.cmake sources QGIS dependency headers from the +# QGIS fork's own build/vcpkg_installed tree, so OpenSRA needs no separate QGIS_DEPS repo. + +list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +# ---- Conan packages (via conanfile.py -> CMakeDeps) ---- +find_package(jansson REQUIRED) +find_package(ZLIB REQUIRED) +find_package(CURL REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) + +# ---- QCA (Qt Cryptography Architecture) from the SimCenter deps tree ---- +# Uses the installed CMake config package QGIS/DEPS/lib/cmake/Qca-qt6 (imported target qca-qt6, +# which carries its QtCrypto include dir + Qt6::Core). REQUIRED for the link (Session 4). +find_package(Qca-qt6 CONFIG REQUIRED) +set(QCA_FOUND TRUE) +set(QCA_LIBRARY qca-qt6) # imported target +set(QCA_INCLUDE_DIR "${PATH_TO_QGIS}/DEPS/include/Qca-qt6/QtCrypto") + +# ---- Qwt + QScintilla from the SimCenter deps tree (mirror R2D's -D flags) ---- +set(QWT_INCLUDE_DIR "${PATH_TO_QGIS}/DEPS/include/qwt" CACHE PATH "Qwt include dir") +set(QWT_LIBRARY "${PATH_TO_QGIS}/DEPS/lib/qwt.lib" CACHE FILEPATH "Qwt import library") +set(QSCINTILLA_INCLUDE_DIR "${PATH_TO_QGIS}/DEPS/include" CACHE PATH "QScintilla include root (has Qsci/)") +set(QSCINTILLA_LIBRARY "${PATH_TO_QGIS}/DEPS/lib/qscintilla2_qt6.lib" CACHE FILEPATH "QScintilla import library") + +find_package(QCA QUIET) +if(QCA_FOUND AND Qt6Keychain_FOUND) + set(OPENSRA_HAVE_QGIS_DEPS TRUE) +else() + set(OPENSRA_HAVE_QGIS_DEPS FALSE) + message(WARNING + "OpenSRA: QCA and/or Qt6Keychain not found yet (QCA_FOUND=${QCA_FOUND}, " + "Qt6Keychain_FOUND=${Qt6Keychain_FOUND}). These come from the QGIS fork's vcpkg build. " + "Configure proceeds, but a full LINK requires them — add the QGIS vcpkg tree to " + "CMAKE_PREFIX_PATH (e.g. /QGIS/build/vcpkg_installed/x64-windows) once built.") +endif() + +# ---- Target ---- +set(TARGET_NAME ${PROJECT_NAME}) # "OpenSRA" + +set(SOURCES + main.cpp +) + +set(HEADERS + # populated by the helper modules below +) + +set(QRC_FILES + images.qrc + styles.qrc +) + +# ---- App icon ---- +set(APP_ICON "") +if(APPLE) + set(APP_ICON "${CMAKE_CURRENT_LIST_DIR}/icons/openSRA-icon.icns") + if(EXISTS "${APP_ICON}") + set_source_files_properties("${APP_ICON}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + else() + message(WARNING "App icon not found: ${APP_ICON}") + set(APP_ICON "") + endif() +elseif(WIN32) + # .rc embeds icons/openSRA-icon.ico (was RC_ICONS in OpenSRA.pro) + set(APP_ICON "${CMAKE_CURRENT_LIST_DIR}/OpenSRA.rc") + if(NOT EXISTS "${APP_ICON}") + message(WARNING "App icon rc not found: ${APP_ICON}") + set(APP_ICON "") + endif() +endif() + +# ---- Executable ---- +qt_add_executable(${TARGET_NAME} + WIN32 + MACOSX_BUNDLE + ${SOURCES} + ${HEADERS} + ${QRC_FILES} + ${APP_ICON} +) + +# ---- Defines (mirror OpenSRA.pro) ---- +target_compile_definitions(${TARGET_NAME} PRIVATE + APP_VERSION="${PROJECT_VERSION}" + OpenSRA + Q_GIS +) + +if(WIN32) + target_compile_definitions(${TARGET_NAME} PRIVATE CURL_STATICLIB) +endif() + +# ---- Shared modules + QGIS + app (mirror R2D's include/call order) ---- +# Common +include("${PATH_TO_COMMON}/Common/Common.cmake") +simcenter_add_common(${TARGET_NAME}) + +# RandomVariables +include("${PATH_TO_COMMON}/RandomVariables/RandomVariables.cmake") +simcenter_add_randomvariables(${TARGET_NAME}) + +# SimCenterQGIS (shared QGIS widgets + QGIS fork include paths) +include("${PATH_TO_COMMON}/QGIS/SimCenterQGIS.cmake") +simcenter_add_qgis(${TARGET_NAME}) + +# OpenSRA app sources (own + borrowed R2D subset) +include("${CMAKE_CURRENT_LIST_DIR}/OpenSRA.cmake") +simcenter_add_opensra(${TARGET_NAME}) + +# Common Workflow — OpenSRA-SELECTIVE set (NOT the full simcenter_add_workflow, which would +# double-compile OpenSRA's own LocalApplication/MainWindowWorkflowApp and pull in unused SIM +# building models -> duplicate-class/moc conflicts). Mirrors OpenSRACommon.pri's Workflow entries. +include("${CMAKE_CURRENT_LIST_DIR}/OpenSRAWorkflow.cmake") +simcenter_add_opensra_workflow(${TARGET_NAME}) + +# ---- Include dirs ---- +if(QCA_FOUND) + target_include_directories(${TARGET_NAME} PRIVATE ${QCA_INCLUDE_DIR}) +endif() +target_include_directories(${TARGET_NAME} PRIVATE + ${QSCINTILLA_INCLUDE_DIR} + ${QWT_INCLUDE_DIR} +) + +# minizip/ZipUtils (SimCenterCommon) must see conan's STATIC zlib.h (plain symbols), not the +# vcpkg SHARED zlib.h that SimCenterQGIS adds to the include path (which yields __imp_* dllimport +# refs unsatisfied by the linked static conan zlib). BEFORE puts conan's zlib include first. +get_target_property(_opensra_zlib_inc ZLIB::ZLIB INTERFACE_INCLUDE_DIRECTORIES) +if(_opensra_zlib_inc) + target_include_directories(${TARGET_NAME} BEFORE PRIVATE ${_opensra_zlib_inc}) +endif() +message(STATUS "OpenSRA: prepended conan zlib include = ${_opensra_zlib_inc}") + +# ---- Qt link libs ---- +target_link_libraries(${TARGET_NAME} PRIVATE + Qt6::Core Qt6::Gui Qt6::Widgets + Qt6::Charts Qt6::Concurrent Qt6::Network Qt6::Sql Qt6::Xml + Qt6::WebEngineWidgets + Qt6::3DCore Qt6::3DRender Qt6::3DExtras + Qt6::OpenGL Qt6::OpenGLWidgets Qt6::Svg + Qt6::Positioning Qt6::QuickWidgets +) + +# qtkeychain (from the QGIS vcpkg tree) — linked only when available; required for link. +if(Qt6Keychain_FOUND) + target_link_libraries(${TARGET_NAME} PRIVATE qt6keychain) +endif() + +# ---- QGIS libraries (PARAMETERIZED; USER sets QGIS_LIB_DIR in Session 4) ---- +# Empty default keeps the configure clean and skips QGIS linking until the fork is built. +set(QGIS_LIB_DIR "${PATH_TO_QGIS}/build/src" CACHE PATH "Path to built QGIS libs (Windows: /QGIS/build/src; macOS: .../Frameworks)") +set(QGIS_VERSION "4.1.0" CACHE STRING "QGIS library version") + +if(QGIS_LIB_DIR) + if(APPLE) + foreach(m app gui native analysis 3d core) + target_link_libraries(${TARGET_NAME} PRIVATE "${QGIS_LIB_DIR}/libqgis_${m}.${QGIS_VERSION}.dylib") + endforeach() + elseif(WIN32) + foreach(m app gui native core) + target_link_libraries(${TARGET_NAME} PRIVATE "${QGIS_LIB_DIR}/${m}/qgis_${m}.lib") + endforeach() + endif() +else() + message(STATUS "OpenSRA: QGIS_LIB_DIR not set -> skipping QGIS library linking (set it in Session 4).") +endif() + +# ---- External libs (prefer find_package results) ---- +target_link_libraries(${TARGET_NAME} PRIVATE + ${QWT_LIBRARY} + ${QSCINTILLA_LIBRARY} + CURL::libcurl + ZLIB::ZLIB + jansson::jansson + nlohmann_json::nlohmann_json +) + +# minizip/ZipUtils (SimCenterCommon) compiles against the vcpkg (shared) zlib headers on the +# QGIS include path and references __imp_* zlib symbols that conan's STATIC zlib doesn't provide. +# Satisfy them with the vcpkg zlib import lib (zlib1.dll is already in-process via the QGIS libs). +if(WIN32 AND EXISTS "${PATH_TO_QGIS}/build/vcpkg_installed/x64-windows/lib/zlib.lib") + target_link_libraries(${TARGET_NAME} PRIVATE "${PATH_TO_QGIS}/build/vcpkg_installed/x64-windows/lib/zlib.lib") +endif() + +# QCA (from the QGIS vcpkg tree) — linked only when available; required for link. +if(QCA_FOUND) + target_link_libraries(${TARGET_NAME} PRIVATE ${QCA_LIBRARY}) +endif() + +if(WIN32) + target_link_libraries(${TARGET_NAME} PRIVATE Advapi32) +elseif(APPLE) + target_link_libraries(${TARGET_NAME} PRIVATE lapack blas) +endif() + +# Qt 6.10 auto-finalizes qt_add_executable targets; an explicit qt_finalize_executable here +# would double-finalize. Left to automatic finalization. diff --git a/GeneralInformationWidget.cpp b/GeneralInformationWidget.cpp index 758f1a3..0b95537 100644 --- a/GeneralInformationWidget.cpp +++ b/GeneralInformationWidget.cpp @@ -62,7 +62,7 @@ GeneralInformationWidget::GeneralInformationWidget(QWidget *parent) : SimCenterA { QVBoxLayout *mainLayout = new QVBoxLayout(); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); mainLayout->setContentsMargins(5,0,0,0); @@ -113,8 +113,15 @@ bool GeneralInformationWidget::outputToJSON(QJsonObject &jsonObj) QDir workDir(workingDirectoryLineEdit->text()); directoryObj.insert("Working",workDir.absolutePath()); - directoryObj.insert("OpenSRAData",OpenSRAPreferences::getInstance()->getAppDataDir()); - directoryObj.insert("NDAData",OpenSRAPreferences::getInstance()->getNDADataDir()); + // OpenSRA datasets folder (Preferences); kept in a variable so NDAData can be compared to it. + auto openSRAData = OpenSRAPreferences::getInstance()->getAppDataDir(); + // Emit OpenSRAData unchanged (only refactored from the original one-liner to reuse the variable). + directoryObj.insert("OpenSRAData", openSRAData); + // NDA datasets folder (Preferences); this field defaults blank or to the OpenSRAData folder. + auto ndaData = OpenSRAPreferences::getInstance()->getNDADataDir(); + // Emit NDAData only when non-empty AND distinct, else the backend merges the dataset folder + // with itself and crashes; a genuine prepackaged-NDA folder is still preserved. + if(!ndaData.isEmpty() && ndaData != openSRAData) directoryObj.insert("NDAData", ndaData); outputObj.insert("AnalysisID",analysisLineEdit->text()); // outputObj.insert("UnitSystem",unitsCombo->currentText()); diff --git a/JsonWidgets/JsonDefinedWidget.cpp b/JsonWidgets/JsonDefinedWidget.cpp index d32e91a..2f38769 100644 --- a/JsonWidgets/JsonDefinedWidget.cpp +++ b/JsonWidgets/JsonDefinedWidget.cpp @@ -52,7 +52,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. JsonDefinedWidget::JsonDefinedWidget(QWidget* parent, const QJsonObject& obj, const QString parentKey) : JsonWidget(parent) { layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0,0,0,0); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -100,7 +100,7 @@ JsonDefinedWidget::JsonDefinedWidget(QWidget* parent, const QJsonObject& obj, co auto returnParam = returnObj.value("Params").toArray(); QVBoxLayout *returnParamLayout = new QVBoxLayout(); - returnParamLayout->setMargin(0); + returnParamLayout->setContentsMargins(0, 0, 0, 0); returnParamLayout->setSpacing(4); if (returnParam.size() > 0) diff --git a/JsonWidgets/JsonLabel.cpp b/JsonWidgets/JsonLabel.cpp index 7bc2022..d93a22c 100644 --- a/JsonWidgets/JsonLabel.cpp +++ b/JsonWidgets/JsonLabel.cpp @@ -38,7 +38,6 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. #include "JsonLabel.h" -#include #include JsonLabel::JsonLabel(QWidget* parent) : QLabel(parent) diff --git a/JsonWidgets/JsonLineEdit.cpp b/JsonWidgets/JsonLineEdit.cpp index 00a9962..8e8612b 100644 --- a/JsonWidgets/JsonLineEdit.cpp +++ b/JsonWidgets/JsonLineEdit.cpp @@ -37,7 +37,6 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. // Written by: Dr. Stevan Gavrilovic, UC Berkeley #include "JsonLineEdit.h" -#include #include JsonLineEdit::JsonLineEdit(QWidget* parent) : QLineEdit(parent) diff --git a/JsonWidgets/SimCenterJsonWidget.cpp b/JsonWidgets/SimCenterJsonWidget.cpp index 76790d1..49a8db6 100644 --- a/JsonWidgets/SimCenterJsonWidget.cpp +++ b/JsonWidgets/SimCenterJsonWidget.cpp @@ -95,7 +95,7 @@ SimCenterJsonWidget::SimCenterJsonWidget(QString methodName, QJsonObject jsonObj QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0,0,0,0); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); auto mainWidget = this->getWidgetBox(jsonObj); @@ -141,6 +141,8 @@ QGroupBox* SimCenterJsonWidget::getWidgetBox(const QJsonObject jsonObj) connect(addRunListWidget,&AddToRunListWidget::addToRunListButtonPressed, this, &SimCenterJsonWidget::handleAddButtonPressed); + connect(addRunListWidget,&AddToRunListWidget::inputsEdited, this, &SimCenterJsonWidget::handleRunListInputsEdited); + groupBoxLayout->addWidget(scrollWidget); // @@ -152,10 +154,10 @@ QGroupBox* SimCenterJsonWidget::getWidgetBox(const QJsonObject jsonObj) if(nameToDisplay == "Landslide Induced Pipe Strain") { QVBoxLayout* inputLayout = new QVBoxLayout(); - inputLayout->setMargin(0); + inputLayout->setContentsMargins(0, 0, 0, 0); strainLandslideCheckBox = new QCheckBox("1) Consider strain preloading (checked = yes)?"); - strainLandslideCheckBox->setChecked(false); + strainLandslideCheckBox->setChecked(true); inputLayout->addWidget(strainLandslideCheckBox); groupBoxLayout->addLayout(inputLayout,Qt::AlignCenter); @@ -164,19 +166,19 @@ QGroupBox* SimCenterJsonWidget::getWidgetBox(const QJsonObject jsonObj) if(nameToDisplay == "Fault Rupture Induced Pipe Strain") { QVBoxLayout* inputLayout = new QVBoxLayout(); - inputLayout->setMargin(0); + inputLayout->setContentsMargins(0, 0, 0, 0); QHBoxLayout* bufferLayout = new QHBoxLayout(); - bufferLayout->setMargin(0); - bufferCheckBox = new QCheckBox("1) Increase buffer on fault traces?"); - bufferCheckBox->setChecked(false); + bufferLayout->setContentsMargins(0, 0, 0, 0); + bufferCheckBox = new QCheckBox("1) Increase buffer on fault traces (UCERF only)?"); + bufferCheckBox->setChecked(true); auto bufferPrimaryLabel = new QLabel("Buffer for Primary Hazard (m):"); bufferPrimaryLineEdit = new QLineEdit(); - bufferPrimaryLineEdit->setEnabled(false); + bufferPrimaryLineEdit->setEnabled(true); bufferPrimaryLineEdit->setText("100"); auto bufferSecondaryLabel = new QLabel("Buffer for Secondary Hazard (m):"); bufferSecondaryLineEdit = new QLineEdit(); - bufferSecondaryLineEdit->setEnabled(false); + bufferSecondaryLineEdit->setEnabled(true); bufferSecondaryLineEdit->setText("100"); bufferLayout->addWidget(bufferCheckBox); bufferLayout->addWidget(bufferPrimaryLabel); @@ -197,7 +199,7 @@ QGroupBox* SimCenterJsonWidget::getWidgetBox(const QJsonObject jsonObj) }); strainFaultRuptureCheckBox = new QCheckBox("2) Consider strain preloading (checked = yes)?"); - strainFaultRuptureCheckBox->setChecked(false); + strainFaultRuptureCheckBox->setChecked(true); inputLayout->addLayout(bufferLayout); inputLayout->addWidget(strainFaultRuptureCheckBox); @@ -210,7 +212,7 @@ QGroupBox* SimCenterJsonWidget::getWidgetBox(const QJsonObject jsonObj) { QVBoxLayout* inputLayout = new QVBoxLayout(); - inputLayout->setMargin(0); + inputLayout->setContentsMargins(0, 0, 0, 0); // widget for additional landslide parameters for deformation polygons to use defPolyLineEdit = new QLineEdit(); @@ -875,6 +877,69 @@ void SimCenterJsonWidget::handleListItemSelected(const QModelIndex& index) } +void SimCenterJsonWidget::handleRunListInputsEdited(void) +{ + auto treeItem = listWidget->getCurrentItem(); + + if(treeItem == nullptr) + return; + + auto itemID = treeItem->getItemID(); + + auto itemObj = listWidget->getItemJsonObject(itemID); + + auto itemKey = itemObj.value("Key").toString(); + + if(itemKey.isEmpty() || !itemObj.contains(itemKey)) + return; + + // Only sync when the method shown in the input panel is the selected item's + // method, so typing ahead of an "Add" for a different method cannot mutate + // the selected item + QJsonObject currMethodObj; + methodWidget->outputToJSON(currMethodObj); + auto currMethod = currMethodObj.value("Method"); + QString currKey; + if(currMethod.isObject()) + { + auto currKeys = currMethod.toObject().keys(); + if(currKeys.size() == 1) + currKey = currKeys.front(); + } + else + currKey = currMethod.toString(); + + if(currKey != itemKey) + return; + + // Re-capture the model weight, aleatory variability, and epistemic uncertainty + auto methodObj = itemObj.value(itemKey).toObject(); + addRunListWidget->outputToJSON(methodObj); + itemObj[itemKey] = methodObj; + addRunListWidget->outputToJSON(itemObj); + + listWidget->updateItemJsonObject(itemID, itemObj); + + auto aleJson = itemObj.value("Aleatory"); + auto aleVal = aleJson.isDouble() ? QString::number(aleJson.toDouble()) : aleJson.toString(); + if (aleVal.isEmpty()) + aleVal = "Preferred"; + auto epiJson = itemObj.value("Epistemic"); + auto epiVal = epiJson.isDouble() ? QString::number(epiJson.toDouble()) : epiJson.toString(); + if (epiVal.isEmpty()) + epiVal = "Preferred"; + + QString newItemText = itemObj.value("ModelName").toString() + + "\n - weight="+ QString::number(itemObj.value("ModelWeight").toDouble()) + + "\n - aleatory="+ aleVal + + "\n - epistemic="+ epiVal; + + treeItem->setData(newItemText, 0); + + listWidget->viewport()->update(); +} + + QJsonObject SimCenterJsonWidget::getVars(const QJsonObject& origObj, const QString& key) diff --git a/JsonWidgets/SimCenterJsonWidget.h b/JsonWidgets/SimCenterJsonWidget.h index 6f0af4b..73eb052 100644 --- a/JsonWidgets/SimCenterJsonWidget.h +++ b/JsonWidgets/SimCenterJsonWidget.h @@ -76,6 +76,8 @@ public slots: void handleListItemSelected(const QModelIndex &index); + void handleRunListInputsEdited(void); + private: QJsonObject getGenericModelObj(QJsonObject& paramObj, QJsonObject& variableTypesObj); diff --git a/LocalApplication.cpp b/LocalApplication.cpp index 2602727..e4f19f4 100644 --- a/LocalApplication.cpp +++ b/LocalApplication.cpp @@ -243,14 +243,30 @@ bool LocalApplication::setupDoneRunPreprocessing(QString &workingDir, QString &/ errorMessage("First time after installation of OpenSRA, this step will take an additional 1 minute (approx.) to complete background tasks..."); statusMessage(""); // switch back to status message format - procEnv.insert("PATH", python); + // Resolve the conda env root (the directory that contains python.exe). + QFileInfo pythonPath(python); + auto condaPrefix = pythonPath.absoluteDir().absolutePath(); + + // PATH must include the conda env's native-DLL dirs (Library\bin holds the GDAL/GEOS/ + // PROJ DLLs); setting it to just python.exe lets steps 1-9 run but crashes the backend + // at the spatial crossing step. Prepend the standard conda activation dirs, then the + // inherited system PATH so OS DLLs stay reachable. +#ifdef Q_OS_WIN + QStringList condaPathDirs = { + condaPrefix, + condaPrefix + "\\Library\\bin", + condaPrefix + "\\Library\\mingw-w64\\bin", + condaPrefix + "\\Library\\usr\\bin", + condaPrefix + "\\Scripts", + condaPrefix + "\\bin" + }; + procEnv.insert("PATH", condaPathDirs.join(";") + ";" + sysEnv.value("PATH")); +#else + procEnv.insert("PATH", condaPrefix + ":" + sysEnv.value("PATH")); +#endif procEnv.insert("PYTHONPATH", python); procEnv.insert("USERNAME", "opensra_user"); procEnv.insert("LOCALAPPDATA", QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); - - // other environment variables needed for OpenSRA - QFileInfo pythonPath(python); - auto condaPrefix = pythonPath.absoluteDir().absolutePath(); procEnv.insert("CONDA_PREFIX", condaPrefix); @@ -423,13 +439,28 @@ bool LocalApplication::setupDoneRunApplication(QString &tmpDirectory, QString &i errorMessage("First time after installation of OpenSRA, this step will take an additional 10 minutes (approx.) to complete background tasks..."); statusMessage(""); // switch back to status message format - procEnv.insert("PATH", python); - procEnv.insert("PYTHONPATH", python); - procEnv.insert("LOCALAPPDATA", QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); - - // other environment variables needed for OpenSRA + // Resolve the conda env root (the directory that contains python.exe). QFileInfo pythonPath(python); auto condaPrefix = pythonPath.absoluteDir().absolutePath(); + + // PATH must include the conda env's native-DLL dirs (Library\bin holds the GDAL/GEOS/ + // PROJ DLLs); setting it to just python.exe crashes the backend at the spatial crossing + // step. Prepend the standard conda activation dirs, then the inherited system PATH. +#ifdef Q_OS_WIN + QStringList condaPathDirs = { + condaPrefix, + condaPrefix + "\\Library\\bin", + condaPrefix + "\\Library\\mingw-w64\\bin", + condaPrefix + "\\Library\\usr\\bin", + condaPrefix + "\\Scripts", + condaPrefix + "\\bin" + }; + procEnv.insert("PATH", condaPathDirs.join(";") + ";" + sysEnv.value("PATH")); +#else + procEnv.insert("PATH", condaPrefix + ":" + sysEnv.value("PATH")); +#endif + procEnv.insert("PYTHONPATH", python); + procEnv.insert("LOCALAPPDATA", QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); procEnv.insert("CONDA_PREFIX", condaPrefix); QString projDataPath = condaPrefix + QDir::separator() + "Library" + QDir::separator() + "share" + QDir::separator() + "proj"; procEnv.insert("PROJ_DATA", projDataPath); @@ -521,7 +552,7 @@ int LocalApplication::handlePreprocessDone(int res) if(res ==0) emit preprocessingDone(); else - this->errorMessage("Error at the \"PREPROCESSING\" step with result "+QString(res)); + this->errorMessage("Error at the \"PREPROCESSING\" step with result "+QString::number(res)); return 0; } @@ -534,7 +565,7 @@ int LocalApplication::handleApplicationRunDone(int res) if(res ==0) emit processResults(QString(),QString(),QString()); else - this->errorMessage("Error at the \"PERFORM ANALYSIS\" step with result "+QString(res)); + this->errorMessage("Error at the \"PERFORM ANALYSIS\" step with result "+QString::number(res)); return 0; } diff --git a/MIGRATION_NOTES.md b/MIGRATION_NOTES.md new file mode 100644 index 0000000..306f3c3 --- /dev/null +++ b/MIGRATION_NOTES.md @@ -0,0 +1,351 @@ +# OpenSRA Qt5→Qt6 / QGIS3→QGIS4 / QMake→CMake migration notes + +Living record of the migration. Records what changed, why, every divergence from the +CLAUDE.md §6 skeleton, and anything observed/needed **outside** `OpenSRAFrontEnd` (for +upstream reporting). Branch: `qt6-qgis4-migration` (off `csv-import-fix`). + +--- + +## Status by session + +| Session | Scope | State | +|---|---|---| +| 1 | Pre-flight | done (prereqs were initially missing; later satisfied by the user) | +| 2 | CMake authoring → clean **configure**; remove dead ArcGIS/plugin paths | **DONE — configure+generate exit 0** | +| 3 | Qt6 renames (§4); delete dead code | **DONE** (only non-QGIS code; full compile pending QGIS deps) | +| 4 | Link → full Release build | **DONE — clean build, `build/Release/OpenSRA.exe` produced** (see "Session 4" below) | + +--- + +## Actual workspace layout (differs from CLAUDE.md §2) + +Siblings live under `C:\dev\OpenSRA_GUI\`: +``` +OpenSRA_GUI/ +├── OpenSRAFrontEnd/ (this repo — target) +├── R2DTool/ (reference; FLAT layout, has CMakeLists.txt + R2D.cmake) +├── SimCenterCommon/ (shared; has QGIS/ + the *.cmake helper modules) +└── QGIS/ (SimCenter QGIS 4 fork; USER is building it) +``` +So under CMake the paths are **single `..`**: `PATH_TO_COMMON=../SimCenterCommon`, +`PATH_TO_R2D=../R2DTool`. (The old QMake `OpenSRA.pro` used `../../…` for the previous +nested layout — not carried over.) **There is no `QGIS_DEPS` repo** (confirmed with the user). + +--- + +## Files added (build system) + +- `CMakeLists.txt` — top-level, mirrors `R2DTool/CMakeLists.txt`. +- `OpenSRA.cmake` — `simcenter_add_opensra(target)`, mirrors `R2D.cmake`. Adds OpenSRA's own + sources (from `OpenSRA.pro`, minus `main.cpp`) **plus** the borrowed R2D source subset + (from `OpenSRACommon.pri`'s `$$PATH_TO_R2D` entries). The SimCenterCommon Workflow / + RandomVariables / Common sources that `OpenSRACommon.pri` + `Common.pri` used to pull are + **not** listed here — they are added by the helper modules (avoids double-listing). +- `conanfile.py` — Conan **v2** (CMakeDeps + CMakeToolchain), mirrors + `R2DTool/conanfile2.py`. Requires jansson, zlib, nlohmann_json, libcurl(non-Linux). +- `msvc_fix.h` — copied verbatim from R2D (`byte`/RPC clash fix + WIN32_LEAN_AND_MEAN/NOMINMAX), + force-included on MSVC via `/FI`. +- `cmake/FindQCA.cmake` — copied verbatim from R2D. +- `.gitignore` — added `build/` and `CMakeUserPresets.json`. + +## Files changed + +- `OpenSRA.pro` — removed the dead `PATH_TO_QGIS_PLUGIN=../../QGISPlugin` + its + `include($$PATH_TO_QGIS_PLUGIN/QGIS.pri)` (the missing Qt5 plugin). Kept as legacy reference + only; CMake is now authoritative. +- `main.cpp:46` — `endl` → `Qt::endl`. +- `UIWidgets/FixedResidualsSamplingWidget.cpp` — removed dead `#include ` + (only usages were already commented out). +- `JsonWidgets/JsonLabel.cpp`, `JsonWidgets/JsonLineEdit.cpp` — removed dead + `#include ` (no live usage in either file). + +## CSV-import fix — the frontend half (inherited from `csv-import-fix`) + +This branch descends from `csv-import-fix`, so it carries the **OpenSRAFrontEnd +half** of the below-ground CSV-pipeline round-trip fix. Because the branch was +squashed into a single migration commit, these two files are not called out in the +commit message — recorded here (the R2DTool commit's message points at this file). +The **R2DTool half** (`UIWidgets/LineAssetInputWidget.cpp`: load-CSV-on-import + +emit `DataType`/`SiteDataFile`/flattened six-key `SiteLocationParams`) lives on the +R2DTool `qt6-fixes` branch (commit `a76582b8`). + +- `GeneralInformationWidget.cpp` — emit `NDAData` only when it is non-empty **and** + distinct from `OpenSRAData` (`if(!ndaData.isEmpty() && ndaData != openSRAData)`). + The prior code injected `NDAData == OpenSRAData`, making the backend merge the + dataset folder onto itself. Affects what a GUI-exported SetupConfig writes. +- `LocalApplication.cpp` (two call sites: preprocess ≈L246, main run ≈L442) — + builds the backend subprocess `PATH` from the conda env's native-DLL dirs + (`Library\bin`, `Library\mingw-w64\bin`, `Library\usr\bin`, `Scripts`, …), not + just the `python.exe` directory. Without it a GUI-launched analysis of a + below-ground CSV network dies at the spatial-crossing step with **exit 127** + (GDAL/GEOS/PROJ DLLs not found); steps 1–9 survive because `GDAL_DATA`/`PROJ_DATA` + are set explicitly. + +No backend code change is part of this fix — the GUI now simply emits the six +`SiteLocationParams` keys + `DataType` that `Preprocess.py` already requires. + +## Files deleted (dead code) + +- `UIWidgets/OpenSRAPostProcessor_old.cpp` + `.h` — legacy postprocessor, not in the build; + held all the Qt5/QGIS3 hot-spots (QtCharts namespace, setPaperSize/get/setPageMargins old + forms, `mainCanvas->extent()`). Deleted per CLAUDE.md §6 (do not port). +- `arcgisruntime.pri` — orphaned ESRI ArcGIS Runtime locator; never `include()`d. Deleted. + +--- + +## Divergences from the CLAUDE.md §6 CMake skeleton (with reasons) + +1. **Conan v2, not v1.** R2D's active recipe is `conanfile2.py` (v2, CMakeDeps+CMakeToolchain); + the v1 `conanfile.py` (generators="qmake") is stale. OpenSRA uses a single v2 `conanfile.py`. +2. **No `QGIS_DEPS/include`.** `SimCenterCommon/QGIS/SimCenterQGIS.cmake` reads **no** caller + variables and sources all QGIS dependency headers from the fork's own + `…/QGIS/build/vcpkg_installed/x64-windows/include` (derived from its own location + `…/SimCenterCommon/QGIS/../../QGIS`). So the skeleton's `${PATH_TO_QGIS_DEPS}/include` was + dropped entirely. +3. **`SerialPort` dropped from Qt components.** Grep shows no `QSerialPort` use anywhere in + OpenSRA's own / SimCenterCommon / borrowed-R2D compiled sources (only inside the QGIS fork, + which is linked, not compiled). Per the brief's "only if used." Re-add only if Session-4 + linking against `qgis_gui` needs it. +4. **`WebEngineWidgets` kept.** `simcenter_add_workflow` adds `SimCenterCommon/Workflow/TOOLS/ + ShakerMaker.*` + `DRM_Model.*`, which use QtWebEngine — so it is transitively required. +5. **QCA + Qt6Keychain are optional at configure, required at link.** Both come from the QGIS + fork's vcpkg build, which is not done yet. `find_package(QCA QUIET)` / `find_package(Qt6Keychain + QUIET)` + an `OPENSRA_HAVE_QGIS_DEPS` guard let configure stay clean now; their include/link + usage is wrapped in `if(QCA_FOUND)` / `if(Qt6Keychain_FOUND)`. **This is a temporary staging + adaptation** — once the QGIS vcpkg tree exists, add it to `CMAKE_PREFIX_PATH` and they engage + automatically (and are enforced at link). +6. **`QGIS_LIB_DIR` parameterized + guarded.** Empty cache default; the link block is wrapped in + `if(QGIS_LIB_DIR)` so configure is clean before the fork is built. WIN32 links 4 modules + (`app gui native core`) from `${QGIS_LIB_DIR}//qgis_.lib`; APPLE links 6. No hard-coded + developer paths (R2D's CMakeLists hard-codes `C:/Users/fmcke/...` — intentionally **not** + mirrored). +7. **libcurl uses the `schannel` SSL backend on Windows** (`-o libcurl/*:with_ssl=schannel`) + instead of the default openssl. Avoids building openssl (which needs nasm + strawberryperl) + and sidesteps an SSL-cert failure reaching conancenter (see Environment issues). Deviation + from R2D's default; revisit if openssl is specifically required. +8. **No explicit `qt_finalize_executable`.** Qt 6.10 auto-finalizes `qt_add_executable`; the + explicit call double-finalized (warning). Removed. +9. **C++20** (mirrors R2D). Note: the Conan profile sets `compiler.cppstd=14`; CMakeLists + overrides to 20 (benign configure warning). Consider setting cppstd=20 in the Conan profile. +10. **`OpenSRAUserPass.h` added conditionally** (`if(EXISTS)`) — it is gitignored/sensitive and + absent on clean checkouts; mirrors R2D's `R2DUserPass.h` handling. +11. **Helper modules invoked:** `simcenter_add_common`, `simcenter_add_randomvariables`, + `simcenter_add_qgis`, `simcenter_add_opensra`, `simcenter_add_workflow` (R2D's order, minus + `simcenter_add_inputsheet` — OpenSRA's QMake never used InputSheetBM; add if a later compile + needs it). + +--- + +## Punch list A — Qt5 deprecated sites (final) + +All live sites resolved; remaining hits were in the now-deleted `_old` file. +| Site | Action | +|---|---| +| `main.cpp:46` bare `endl` | → `Qt::endl` ✓ | +| `FixedResidualsSamplingWidget.cpp:9` `#include ` | removed (dead) ✓ | +| `JsonLabel.cpp:41` `#include ` | removed (dead) ✓ | +| `JsonLineEdit.cpp:40` `#include ` | removed (dead) ✓ | +| `OpenSRAPostProcessor_old.cpp/.h` (QtCharts ns, setPaperSize, set/getPageMargins) | file deleted ✓ | + +No `QStringRef`, `QtCharts::`, `QPalette::Background`, `QFileInfo f = QFile(`, or `.indexIn(` +anywhere in the live tree. + +## Punch list B — QGIS-including files (for Session 5) + +13 files include QGIS headers; **all are "light"** (they pass geometry types as strings to the +shared `QGISVisualizationWidget` and delegate canvas/extent/layer-removal to it). No live file +uses `QgsWkbTypes::*Geometry`, direct canvas `setExtent`/`extent()`, or `removeMapLayer` — the +only file that did was `OpenSRAPostProcessor_old.cpp` (deleted). Several already use the new +`Qgis::` enums (`Qgis::SymbolType`, `Qgis::MarkerShape`, `Qgis::RasterResamplingStage`). So +Session 5 should be mostly header/include verification once the build links; the §5 runtime +fixes live in the already-ported `SimCenterCommon/QGIS/QGISVisualizationWidget`. + +Files: `OpenSRAPreProcessor.cpp`, `OpenSRAPostProcessor.cpp`, `CustomVisualizationWidget.cpp`, +`UserInputCPTWidget.cpp/.h`, `UserDefinedGroundMotionWidget.cpp/.h`, `BayAreaPipelineWidget.cpp`, +`LosAngelesPipelineWidget.cpp`, `NDABayAreaPipelineWidget.cpp`, `NDAStateWidePipelineWidget.cpp`, +`StateWidePipelineWidget.cpp` (+ trivial `main.cpp`, `WorkflowAppOpenSRA.cpp`). + +--- + +## Configure procedure (reproducible) + +``` +cd OpenSRA_GUI/OpenSRAFrontEnd +conan install . --output-folder=build --build=missing -nr -s build_type=Release -o "libcurl/*:with_ssl=schannel" +cmake -S . -B build -G "Visual Studio 17 2022" \ + -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ + -DCMAKE_POLICY_DEFAULT_CMP0091=NEW \ + -DCMAKE_PREFIX_PATH="C:/Qt/6.10.2/msvc2022_64" +``` +Result: `-- Configuring done` / `-- Generating done`, exit 0, `build/OpenSRA.sln` generated. +MSVC 19.44 (VS 2022 BuildTools) + Qt 6.10.2 (all components present) detected. Once the QGIS +vcpkg tree exists, append `;C:/dev/OpenSRA_GUI/QGIS/build/vcpkg_installed/x64-windows` to +`-DCMAKE_PREFIX_PATH` (and set `-DQGIS_LIB_DIR=...` in Session 4). + +--- + +## Changes / observations OUTSIDE OpenSRAFrontEnd (for upstream reporting) + +- (Sessions 1–3 were read-only across repos.) **Session 4 required minimal edits to R2DTool** + (borrowed source with Qt5 residue) — see the "Session 4" section below. SimCenterCommon and the + QGIS fork were **not** edited (the workflow-composition divergence was handled OpenSRA-side). +- `SimCenterCommon/QGIS/SimCenterQGIS.cmake` hard-assumes the QGIS fork is a sibling **two + levels up** (`SimCenterCommon/QGIS/../../QGIS`) and already **built** (references `build/…` + and `build/vcpkg_installed//include`). Holds for our layout. No fork header-export + patch needed so far. +- `R2DTool/CMakeLists.txt` hard-codes developer QGIS paths (`C:/Users/fmcke/...`, + `/Users/fmckenna/...`). OpenSRA parameterizes via `QGIS_LIB_DIR` instead — recommend the same + cleanup upstream in R2D. + +## Environment issues for the user to resolve + +- **Conan ↔ conancenter SSL failure:** `CERTIFICATE_VERIFY_FAILED` reaching + `center2.conan.io`. Worked around for now by building from local cache (`-nr`) + schannel + (no openssl). To restore full Conan remote access (needed if deps aren't cached), fix the CA + bundle, e.g. set `conan config` `core.net.http:cacert_path` or env `SSL_CERT_FILE` / + `REQUESTS_CA_BUNDLE` to the corporate/system CA. This will also matter for the QGIS vcpkg build. +- **QCA + Qt6Keychain** come from the QGIS fork's vcpkg build (not done yet). Configure is clean + without them; LINK (Session 4) needs them on `CMAKE_PREFIX_PATH`. + +--- + +## Session 4 (link) — COMPLETE → `build/Release/OpenSRA.exe` (clean build, 0 errors) + +QGIS fork now built (`../QGIS/build/src` + `../QGIS/DEPS` + `../QGIS/build/vcpkg_installed`). Drove +the compile+link from 186 errors to a clean build. Generator: **VS 17 2022**, conan toolchain, **Release**. + +**CMakeLists.txt — DEPS/QGIS wiring (this repo):** +- Appended `../QGIS/DEPS` and `../QGIS/build/vcpkg_installed/x64-windows` to `CMAKE_PREFIX_PATH`. +- QCA via `find_package(Qca-qt6 CONFIG REQUIRED)` (imported target `qca-qt6`); Qt6Keychain via its config + (target `qt6keychain`); Qwt/QScintilla pointed at `DEPS/lib` + `DEPS/include`; `QGIS_LIB_DIR=../QGIS/build/src`. + (Supersedes the §6/divergence-#5 "optional QCA/Qt6Keychain" staging — they are now REQUIRED and resolve from DEPS.) + +**CMake composition — replaced `simcenter_add_workflow`:** OpenSRA ships its OWN `LocalApplication`, +`MainWindowWorkflowApp`, `GeneralInformationWidget`; the full `simcenter_add_workflow` also compiled +SimCenterCommon's same-named classes + SIM building models → duplicate-class/AUTOMOC conflicts. Added +**`OpenSRAFrontEnd/OpenSRAWorkflow.cmake`** (`simcenter_add_opensra_workflow`) with only the 7 Workflow +sources `OpenSRACommon.pri` used (incl. `AnimatedStackedWidget.h` for AUTOMOC). SimCenterCommon untouched. + +**OpenSRA-side Qt6/QGIS4 source fixes:** `setMargin`→`setContentsMargins` (48 sites); `QString::SkipEmptyParts` +→`Qt::SkipEmptyParts`; `QgsRasterDataProvider::ResamplingMethod::Bilinear`→`Qgis::RasterResamplingMethod::Bilinear`; +`QString(int)`→`QString::number`; ``/`QApplication::desktop()->screenGeometry()`→ +`QGuiApplication::primaryScreen()->geometry()`; `QJsonValue["x"]`→`.toObject().value("x")`; `jsonKeyword` +(private in R2D `MultiComponentR2D`) → inline the per-subclass literal; `insertWidgetIntoLayout` commented +(matches siblings); `IntensityMeasureWidget.cpp` += `#include "QGISVisualizationWidget.h"`; +**`OpenSRAPreferences.h` include-guard renamed** `SIMCENTER_PREFERENCES_H`→`OPENSRA_PREFERENCES_H` (it collided +with SimCenterCommon's `SimCenterPreferences.h` guard, blanking that class in the AUTOMOC aggregation → 24 errors). + +**R2DTool — minimal Qt6 edits (borrowed source; user-approved):** +- `UIWidgets/AssetInputWidget.cpp` — `setMargin`→`setContentsMargins`; `hideCRS_Selection()`/`hideAssetFilePath()` + used `QGridLayout` API (`columnCount`/`itemAtPosition`) on a `QVBoxLayout` → rewritten to `itemAt(index)`. +- `UIWidgets/{GISGasNetworkInputWidget,GISAboveGroundGasComponentInputWidget,GISWellsCaprocksInputWidget}.cpp` — `setMargin`→`setContentsMargins`. +- `ModelViewItems/MutuallyExclusiveListWidget.cpp` — `QString::SkipEmptyParts`→`Qt::SkipEmptyParts`. + +**SimCenterCommon — runtime-crash fix (shared source; user-approved, upstream-reportable):** +- `QGIS/SimCenterMapcanvasWidget.cpp` (~line 68) — disabled the two connections that wire the embedded + `legendTreeView`'s `selectionModel` to `QgisApp::updateNewLayerInsertionPoint` (`currentChanged`) and + `QgisApp::legendLayerSelectionChanged` (`selectionChanged`). `legendTreeView` is a *second* `QgsLayerTreeView` + sharing QgisApp's model; on layer removal it emits those signals for the just-deleted (dangling) node, and + QgisApp's slots then read QgisApp's *own* layer-tree view against that node → `0xC0000005`. QgisApp's own view + already drives these with correct timing and the embedded viz is read-only, so the connections are unneeded. + **Fixes the ~24s startup crash** (verified: 60 s under cdb, no fatal exception — see Session 7 below). Likely + benefits R2D too; candidate for upstream. + +**zlib link:** minizip/ZipUtils (SimCenterCommon) compiles against the vcpkg (shared) zlib headers on the QGIS +include path and references `__imp_*` zlib symbols that conan's STATIC zlib doesn't provide. Linked the vcpkg +zlib import lib (`../QGIS/build/vcpkg_installed/x64-windows/lib/zlib.lib`); its `zlib1.dll` is already in-process +via QGIS. (The earlier `BEFORE`-prepend of conan's zlib include is redundant — conan marks its includes +`/external:I`, searched after the regular `/I`; harmless, can be removed in cleanup.) + +**Result:** `cmake --build build --config Release` → 0 errors → `build/Release/OpenSRA.exe` (2.1 MB). +**Not yet done:** running the app (needs the runtime DLLs on PATH: Qt6 `bin`, `../QGIS/build/output/bin`, +`../QGIS/build/vcpkg_installed/x64-windows/bin`, `../QGIS/DEPS/bin`) + QGIS providers/resources — Session 6/7. + +--- + +## Session 7 (runtime / packaging) — bundle manifest + a runtime crash + +**Launch:** OpenSRA.exe LAUNCHES (main window "OpenSRA" shows). It originally **crashed ~24s later, on its own**, +with `0xC0000005` in `qgis_gui.dll` (`QgisApp::updateNewLayerInsertionPoint`). **RESOLVED** by the +`SimCenterMapcanvasWidget.cpp` connection fix above — verified by running under cdb (`sxd av; g`) for 60 s with +**no fatal exception** (app loaded the platform plugin / SQLite / GPU stack and kept running; only benign +`QObject::connect(... invalid nullptr ...)` warnings). The original-crash details below are kept for the record. +Reproduces with PATH or self-contained +bundle, with or without qt.conf → it is a runtime bug, not a packaging gap. + +**Runtime DLL manifest (static closure = 110 non-system DLLs), by source:** +- **Qt (35)** — `C:\Qt\6.10.2\msvc2022_64\bin`, deployed via `windeployqt`. Incl. Qt6Core5Compat (QCA needs it). +- **QGIS + vcpkg deps (71)** — `../QGIS/build/output/bin` and `../QGIS/build/output` (root). qgis_core/gui/app/3d/analysis/native, + gdal, geos/geos_c, proj_9, spatialite, sqlite3, zlib1, **libcrypto-3-x64 + libssl-3-x64** (qca-ossl runtime), arrow/parquet, poppler, etc. + (`vcpkg_installed/x64-windows/bin` is redundant — output already has them.) +- **DEPS (3)** — `../QGIS/DEPS/bin`: qca-qt6, qscintilla2_qt6, qt6keychain. +- **qwt.dll (1)** — `../QGIS/DEPS/**lib**` (GOTCHA: Qwt installs its DLL to `lib/`, not `bin/`). Sole reason a 4-folder PATH gave `0xC0000135`. + +**Qt plugins:** `windeployqt` under-deploys for a QGIS host (it only follows the EXE's own Qt deps). The **full** +`C:\Qt\6.10.2\msvc2022_64\plugins` tree is required — most importantly **`sqldrivers\qsqlite.dll`** (QGIS opens srs.db/gpkg via QSQLITE); +without it `qgis_gui` crashes in init. Bundle = copy the whole `plugins` tree next to the exe. + +**QGIS providers + resources:** the SimCenter QGIS code calls `QgsApplication::initQgis()` but never `setPrefixPath`, so on THIS +machine QGIS uses its compiled-in prefix `../QGIS/build/output` → providers in `output/plugins`, resources in `output/data` +(`data/resources/qgis.db`, `data/svg`). For a PORTABLE bundle: copy `output/plugins` + `output/data` next to the exe and set +`QGIS_PREFIX_PATH` (or add `QgsApplication::setPrefixPath(applicationDirPath(), true)`). NOTE: `srs.db` is at `../QGIS/build/resources/srs.db`, +not under `output/data/resources` — verify QGIS finds the CRS db at runtime. + +**QCA ossl plugin (runtime auth, not needed for the gpkg visual test):** `../QGIS/DEPS/lib/qca-qt6/crypto/qca-ossl.dll` (+ libcrypto/libssl, already in the DLL set). + +**Bundle assembled into `OpenSRAFrontEnd/build/Release`:** windeployqt (Qt + plugins) + the 110-DLL closure + full Qt plugin tree. +A clean-PATH (self-contained) launch loads all modules — but hits the same ~24s crash below. + +**RUNTIME CRASH (blocker, Session 5/6):** ~24s after launch, AV `0xC0000005`, faulting `qgis_gui.dll`. cdb stack +(nearest-export symbols; no QGIS PDBs): +`QgsLayerTreeRegistryBridge::layersWillBeRemoved → QgsLayerTreeGroup::removeChildNode → QgsLayerTreeNode::removeChildrenPrivate +→ QMetaObject::activate → QgisApp::updateNewLayerInsertionPoint → AV`. I.e. a layer-removal signal drives the embedded +`QgisApp`'s new-layer-insertion-point slot into an invalid deref. Diagnose vs R2D's QgisApp/layer-tree setup; a RelWithDebInfo +QGIS (PDBs) would give exact lines. + +--- + +## 2026-07-20 update — packaging validation round (icon, direct launch, PROJ data, crash + serialization fixes) + +Found while packaging the fully self-contained distribution and batch-testing all 33 +bundled backend examples from it. Frontend changes (this repo): + +- **Windows app icon restored** — QMake's `RC_ICONS` had no CMake equivalent; added + `OpenSRA.rc` + a `WIN32` branch in the CMakeLists app-icon block. +- **Direct launch (R2D-style)** — `main.cpp` defaults `QGIS_PREFIX_PATH` to the exe dir + when unset (double-clicking `OpenSRA.exe` now works without the launcher .cmd) and + points `PROJ_DATA` at a shipped `share/proj` when present (the packaged app previously + had no GUI-side proj.db at all; QGIS bundles proj data only on macOS). +- **End-of-analysis hardening** — `importResults` wrapped in try/catch (an uncaught + QString terminated the Qt6 app with no dialog), null guards for empty results-layer + lists (`OpenSRAPostProcessor`, `CustomVisualizationWidget`); results-layer opacity + matched by source URI (all sublayers are renamed on import). +- **Save/load round-trip fixes** — `RandomVariablesWidget` emitted garbage + `\rvs_input.csv` paths (and injected `runDir: null`) in File→Save; + `GenericModelWidget` silently skipped emission when the workdir `Input` was missing + and mis-joined its fallback path; `PipelineNetworkWidget` now propagates + infrastructure serialization failures; `UserDefinedGroundMotionWidget` no longer + rewrites an empty ShakeMap folder to the cwd; crash guards for short CSV rows + (`UserInputCPTWidget`, `RandomVariablesWidget`) and a null line-edit + (`ResultsWidget.h`). + +Changes OUTSIDE this repo (for upstream reporting): + +- **SimCenterCommon `qt6-fixes`**: QGIS-4 use-after-free — `classificationMethodRegistry() + ->method()` now returns an owning `unique_ptr`; the migrated code passed a borrowed + pointer to `setClassificationMethod()` (which takes ownership), crashing on the first + repaint after a graduated renderer was installed (i.e. at end of every analysis). + Fixed with `release()`. Also: `zoomToLayer` guards null extents and catches + `QgsCsException` (was the source of "Could not transform bounding box to target CRS"). +- **R2DTool `qt6-fixes`**: GIS infrastructure serialization emitted a truncated + directory-only `SiteDataFile` (a C++20 `QJsonObject` aliasing no-op: + `obj["a"] = obj["b"]` inserts the LHS key after taking the RHS ref) and assumed a + copy step OpenSRA disables — now emits the absolute source path; `ShakeMapWidget` + bool loader returned `-1` (=true) on failure; per-row bounds checks in + `ComponentTableModel`. +- **Backend (not a git repo)**: 21 patches + 3 data restorations documented in + `Backend_Pandas3_Patches_Round2.md` (pandas-3/numpy-2/geopandas-1 compatibility, + stale UCERF rupture CSV, missing NGA-West2 GMM tables, wrong-results fixes incl. + vs30_source sigma treatment and a positions-as-labels crossing bug). All 28 runnable + examples pass end-to-end from the package; the 5 expected-fail examples fail with + their designed errors. diff --git a/MainWindowWorkflowApp.cpp b/MainWindowWorkflowApp.cpp index 856a743..7b22d54 100644 --- a/MainWindowWorkflowApp.cpp +++ b/MainWindowWorkflowApp.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -472,7 +471,7 @@ void MainWindowWorkflowApp::about() // // adjust size of application window to the available display // - QRect rec = QApplication::desktop()->screenGeometry(); + QRect rec = QGuiApplication::primaryScreen()->geometry(); int height = 0.50*rec.height(); int width = 0.50*rec.width(); dlg->resize(width, height); diff --git a/OpenSRA.cmake b/OpenSRA.cmake new file mode 100644 index 0000000..979fa03 --- /dev/null +++ b/OpenSRA.cmake @@ -0,0 +1,260 @@ +# OpenSRA.cmake +# Usage: +# include(path/to/OpenSRA.cmake) +# simcenter_add_opensra() +# +# Mirrors R2DTool/R2D.cmake. Adds OpenSRA's own sources/headers (from OpenSRA.pro, minus +# main.cpp which is listed at top level) plus the subset of R2DTool sources OpenSRA borrows +# (from OpenSRACommon.pri's $$PATH_TO_R2D entries). +# +# NOTE: the SimCenterCommon Workflow + RandomVariables + Common sources that OpenSRACommon.pri +# and Common.pri used to pull in are intentionally NOT listed here. They are supplied by the +# helper modules invoked from CMakeLists.txt (simcenter_add_workflow / simcenter_add_randomvariables +# / simcenter_add_common). Listing them here too would double-add the same source files. + +set(OPENSRA_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}") + +function(simcenter_add_opensra target) + + set(dir "${OPENSRA_MODULE_DIR}") + set(r2d "${dir}/../R2DTool") + + target_include_directories(${target} PRIVATE + # OpenSRA's own dirs (OpenSRA.pro INCLUDEPATH) + "${dir}" + "${dir}/Utils" + "${dir}/styles" + "${dir}/UIWidgets" + "${dir}/JsonWidgets" + "${dir}/ModelViewItems" + # Borrowed R2DTool dirs (OpenSRACommon.pri INCLUDEPATH) + "${r2d}" + "${r2d}/UIWidgets" + "${r2d}/Tools" + "${r2d}/Events" + "${r2d}/Events/UI" + "${r2d}/GraphicElements" + "${r2d}/ModelViewItems" + "${r2d}/systemPerformanceWidgets" + ) + + set(SOURCES + # ---- OpenSRA's own sources (OpenSRA.pro, minus main.cpp) ---- + "${dir}/JsonWidgets/JsonDefinedWidget.cpp" + "${dir}/JsonWidgets/JsonGroupBoxWidget.cpp" + "${dir}/JsonWidgets/JsonComboBox.cpp" + "${dir}/JsonWidgets/JsonLineEdit.cpp" + "${dir}/JsonWidgets/JsonLabel.cpp" + "${dir}/JsonWidgets/JsonCheckBox.cpp" + "${dir}/JsonWidgets/JsonWidget.cpp" + "${dir}/JsonWidgets/JsonStackedWidget.cpp" + "${dir}/JsonWidgets/SimCenterJsonWidget.cpp" + "${dir}/ModelViewItems/ComboBoxDelegate.cpp" + "${dir}/ModelViewItems/ButtonDelegate.cpp" + "${dir}/ModelViewItems/RV.cpp" + "${dir}/ModelViewItems/RVTableView.cpp" + "${dir}/ModelViewItems/RVTableModel.cpp" + "${dir}/ModelViewItems/MixedDelegate.cpp" + "${dir}/ModelViewItems/LineEditDelegate.cpp" + "${dir}/ModelViewItems/LabelDelegate.cpp" + "${dir}/ModelViewItems/StringListDelegate.cpp" + "${dir}/UIWidgets/AddToRunListWidget.cpp" + "${dir}/UIWidgets/ClickableLabel.cpp" + "${dir}/UIWidgets/EDPLandslideWidget.cpp" + "${dir}/UIWidgets/MonteCarloSamplingWidget.cpp" + "${dir}/UIWidgets/FixedResidualsSamplingWidget.cpp" + "${dir}/UIWidgets/OpenSHAWidget.cpp" + "${dir}/UIWidgets/OpenSRAComponentSelection.cpp" + "${dir}/UIWidgets/OpenSRAPostProcessor.cpp" + "${dir}/UIWidgets/OpenSRAPreProcessor.cpp" + "${dir}/UIWidgets/UncertaintyQuantificationWidget.cpp" + "${dir}/UIWidgets/WidgetFactory.cpp" + "${dir}/UIWidgets/DecisionVariableWidget.cpp" + "${dir}/UIWidgets/SourceCharacterizationWidget.cpp" + "${dir}/UIWidgets/CustomVisualizationWidget.cpp" + "${dir}/UIWidgets/PipelineNetworkWidget.cpp" + "${dir}/UIWidgets/EngDemandParamWidget.cpp" + "${dir}/UIWidgets/EngineeringDemandParameterWidget.cpp" + "${dir}/UIWidgets/MultiComponentEDPWidget.cpp" + "${dir}/UIWidgets/MultiComponentDMWidget.cpp" + "${dir}/UIWidgets/MultiComponentDVWidget.cpp" + "${dir}/UIWidgets/IntensityMeasureWidget.cpp" + "${dir}/UIWidgets/DamageMeasureWidget.cpp" + "${dir}/UIWidgets/RandomVariablesWidget.cpp" + "${dir}/UIWidgets/UserInputCPTWidget.cpp" + "${dir}/UIWidgets/GeospatialDataWidget.cpp" + "${dir}/UIWidgets/GenericModelWidget.cpp" + "${dir}/UIWidgets/StateWidePipelineWidget.cpp" + "${dir}/UIWidgets/BayAreaPipelineWidget.cpp" + "${dir}/UIWidgets/LosAngelesPipelineWidget.cpp" + "${dir}/UIWidgets/NDAStateWidePipelineWidget.cpp" + "${dir}/UIWidgets/NDABayAreaPipelineWidget.cpp" + "${dir}/UIWidgets/UserDefinedGroundMotionWidget.cpp" + "${dir}/Utils/EventFilter.cpp" + "${dir}/ResultsWidget.cpp" + "${dir}/WorkflowAppOpenSRA.cpp" + "${dir}/WorkflowAppWidget.cpp" + "${dir}/MainWindowWorkflowApp.cpp" + "${dir}/LocalApplication.cpp" + "${dir}/OpenSRAPreferences.cpp" + "${dir}/GeneralInformationWidget.cpp" + "${dir}/RunWidget.cpp" + + # ---- Borrowed R2DTool sources (OpenSRACommon.pri, $$PATH_TO_R2D) ---- + "${r2d}/Tools/CSVReaderWriter.cpp" + "${r2d}/Tools/XMLAdaptor.cpp" + "${r2d}/Tools/AssetInputDelegate.cpp" + "${r2d}/Tools/AssetFilterDelegate.cpp" + "${r2d}/Tools/ComponentDatabase.cpp" + "${r2d}/Tools/ComponentDatabaseManager.cpp" + "${r2d}/Tools/TablePrinter.cpp" + "${r2d}/Tools/SimCenterUnitsCombo.cpp" + "${r2d}/Events/UI/SiteConfig.cpp" + "${r2d}/Events/UI/site.cpp" + "${r2d}/Events/UI/SiteGrid.cpp" + "${r2d}/Events/UI/GridDivision.cpp" + "${r2d}/Events/UI/Location.cpp" + "${r2d}/Events/UI/SiteScatter.cpp" + "${r2d}/UIWidgets/LoadResultsDialog.cpp" + "${r2d}/UIWidgets/MultiComponentR2D.cpp" + "${r2d}/GraphicElements/GridNode.cpp" + "${r2d}/GraphicElements/NodeHandle.cpp" + "${r2d}/GraphicElements/RectangleGrid.cpp" + "${r2d}/UIWidgets/LineAssetInputWidget.cpp" + "${r2d}/UIWidgets/PointAssetInputWidget.cpp" + "${r2d}/UIWidgets/CSVWellsCaprocksInputWidget.cpp" + "${r2d}/UIWidgets/GISWellsCaprocksInputWidget.cpp" + "${r2d}/UIWidgets/CSVAboveGroundGasComponentInputWidget.cpp" + "${r2d}/UIWidgets/GISAboveGroundGasComponentInputWidget.cpp" + "${r2d}/UIWidgets/AssetInputWidget.cpp" + "${r2d}/UIWidgets/CRSSelectionWidget.cpp" + "${r2d}/UIWidgets/ShakeMapWidget.cpp" + "${r2d}/UIWidgets/GISAssetInputWidget.cpp" + "${r2d}/UIWidgets/GISMapWidget.cpp" + "${r2d}/UIWidgets/GISGasNetworkInputWidget.cpp" + "${r2d}/UIWidgets/GroundMotionStation.cpp" + "${r2d}/UIWidgets/GroundMotionTimeHistory.cpp" + "${r2d}/UIWidgets/SimCenterUnitsWidget.cpp" + "${r2d}/UIWidgets/UserInputFaultWidget.cpp" + "${r2d}/ModelViewItems/CustomListWidget.cpp" + "${r2d}/ModelViewItems/MutuallyExclusiveListWidget.cpp" + "${r2d}/ModelViewItems/ComponentTableModel.cpp" + "${r2d}/ModelViewItems/ComponentTableView.cpp" + "${r2d}/ModelViewItems/ListTreeModel.cpp" + ) + + set(HEADERS + # ---- OpenSRA's own headers (OpenSRA.pro) ---- + "${dir}/JsonWidgets/JsonDefinedWidget.h" + "${dir}/JsonWidgets/JsonComboBox.h" + "${dir}/JsonWidgets/JsonLineEdit.h" + "${dir}/JsonWidgets/JsonLabel.h" + "${dir}/JsonWidgets/JsonCheckBox.h" + "${dir}/JsonWidgets/JsonWidget.h" + "${dir}/JsonWidgets/JsonStackedWidget.h" + "${dir}/JsonWidgets/SimCenterJsonWidget.h" + "${dir}/JsonWidgets/JsonGroupBoxWidget.h" + "${dir}/ModelViewItems/ComboBoxDelegate.h" + "${dir}/ModelViewItems/ButtonDelegate.h" + "${dir}/ModelViewItems/RV.h" + "${dir}/UIWidgets/AddToRunListWidget.h" + "${dir}/UIWidgets/ClickableLabel.h" + "${dir}/UIWidgets/EDPLandslideWidget.h" + "${dir}/UIWidgets/MonteCarloSamplingWidget.h" + "${dir}/UIWidgets/FixedResidualsSamplingWidget.h" + "${dir}/UIWidgets/OpenSHAWidget.h" + "${dir}/UIWidgets/OpenSRAComponentSelection.h" + "${dir}/UIWidgets/OpenSRAPostProcessor.h" + "${dir}/UIWidgets/OpenSRAPreProcessor.h" + "${dir}/ResultsWidget.h" + "${dir}/UIWidgets/UncertaintyQuantificationWidget.h" + "${dir}/UIWidgets/WidgetFactory.h" + "${dir}/UIWidgets/RandomVariablesWidget.h" + "${dir}/UIWidgets/UserInputCPTWidget.h" + "${dir}/UIWidgets/GeospatialDataWidget.h" + "${dir}/UIWidgets/GenericModelWidget.h" + "${dir}/UIWidgets/StateWidePipelineWidget.h" + "${dir}/UIWidgets/BayAreaPipelineWidget.h" + "${dir}/UIWidgets/LosAngelesPipelineWidget.h" + "${dir}/UIWidgets/NDAStateWidePipelineWidget.h" + "${dir}/UIWidgets/NDABayAreaPipelineWidget.h" + "${dir}/Utils/EventFilter.h" + "${dir}/WorkflowAppOpenSRA.h" + "${dir}/WorkflowAppWidget.h" + "${dir}/MainWindowWorkflowApp.h" + "${dir}/LocalApplication.h" + "${dir}/OpenSRAPreferences.h" + "${dir}/RunWidget.h" + "${dir}/UIWidgets/DecisionVariableWidget.h" + "${dir}/UIWidgets/SourceCharacterizationWidget.h" + "${dir}/UIWidgets/CustomVisualizationWidget.h" + "${dir}/UIWidgets/PipelineNetworkWidget.h" + "${dir}/UIWidgets/EngDemandParamWidget.h" + "${dir}/UIWidgets/EngineeringDemandParameterWidget.h" + "${dir}/UIWidgets/MultiComponentEDPWidget.h" + "${dir}/UIWidgets/MultiComponentDMWidget.h" + "${dir}/UIWidgets/MultiComponentDVWidget.h" + "${dir}/GeneralInformationWidget.h" + "${dir}/UIWidgets/IntensityMeasureWidget.h" + "${dir}/UIWidgets/DamageMeasureWidget.h" + "${dir}/UIWidgets/UserDefinedGroundMotionWidget.h" + "${dir}/ModelViewItems/RVTableView.h" + "${dir}/ModelViewItems/RVTableModel.h" + "${dir}/ModelViewItems/MixedDelegate.h" + "${dir}/ModelViewItems/LineEditDelegate.h" + "${dir}/ModelViewItems/LabelDelegate.h" + "${dir}/ModelViewItems/StringListDelegate.h" + + # ---- Borrowed R2DTool headers (OpenSRACommon.pri, $$PATH_TO_R2D) ---- + "${r2d}/Tools/XMLAdaptor.h" + "${r2d}/Tools/CSVReaderWriter.h" + "${r2d}/Tools/AssetInputDelegate.h" + "${r2d}/Tools/AssetFilterDelegate.h" + "${r2d}/Tools/ComponentDatabase.h" + "${r2d}/Tools/TablePrinter.h" + "${r2d}/Tools/SimCenterUnitsCombo.h" + "${r2d}/Tools/ComponentDatabaseManager.h" + "${r2d}/Events/UI/SiteConfig.h" + "${r2d}/Events/UI/site.h" + "${r2d}/Events/UI/SiteGrid.h" + "${r2d}/Events/UI/GridDivision.h" + "${r2d}/Events/UI/Location.h" + "${r2d}/Events/UI/SiteScatter.h" + "${r2d}/Events/UI/JsonSerializable.h" + "${r2d}/UIWidgets/LoadResultsDialog.h" + "${r2d}/UIWidgets/MultiComponentR2D.h" + "${r2d}/GraphicElements/GridNode.h" + "${r2d}/GraphicElements/NodeHandle.h" + "${r2d}/GraphicElements/RectangleGrid.h" + "${r2d}/UIWidgets/LineAssetInputWidget.h" + "${r2d}/UIWidgets/PointAssetInputWidget.h" + "${r2d}/UIWidgets/CSVWellsCaprocksInputWidget.h" + "${r2d}/UIWidgets/GISWellsCaprocksInputWidget.h" + "${r2d}/UIWidgets/CSVAboveGroundGasComponentInputWidget.h" + "${r2d}/UIWidgets/GISAboveGroundGasComponentInputWidget.h" + "${r2d}/UIWidgets/AssetInputWidget.h" + "${r2d}/UIWidgets/CRSSelectionWidget.h" + "${r2d}/UIWidgets/ShakeMapWidget.h" + "${r2d}/UIWidgets/GISAssetInputWidget.h" + "${r2d}/UIWidgets/GISMapWidget.h" + "${r2d}/UIWidgets/GISGasNetworkInputWidget.h" + "${r2d}/UIWidgets/GroundMotionStation.h" + "${r2d}/UIWidgets/GroundMotionTimeHistory.h" + "${r2d}/UIWidgets/SimCenterUnitsWidget.h" + "${r2d}/UIWidgets/UserInputFaultWidget.h" + "${r2d}/ModelViewItems/CustomListWidget.h" + "${r2d}/ModelViewItems/MutuallyExclusiveListWidget.h" + "${r2d}/ModelViewItems/ComponentTableModel.h" + "${r2d}/ModelViewItems/ComponentTableView.h" + "${r2d}/ModelViewItems/ListTreeModel.h" + ) + + target_sources(${target} PRIVATE ${SOURCES} ${HEADERS}) + + # OpenSRAUserPass.h holds credentials and is gitignored (absent on clean checkouts). + # Add it only when present, mirroring R2D's R2DUserPass.h handling. + if(EXISTS "${dir}/OpenSRAUserPass.h") + target_sources(${target} PRIVATE "${dir}/OpenSRAUserPass.h") + endif() + +endfunction() diff --git a/OpenSRA.pro b/OpenSRA.pro index 767d31a..6139e6e 100644 --- a/OpenSRA.pro +++ b/OpenSRA.pro @@ -45,14 +45,15 @@ win32 { # GIS plugin DEFINES += Q_GIS -PATH_TO_QGIS_PLUGIN=../../R2DTool/qgisplugin -include($$PATH_TO_QGIS_PLUGIN/QGIS.pri) +# NOTE (Qt6/QGIS4 migration): the old prebuilt Qt5 QGIS plugin path was removed. +# The CMake build now provides QGIS via SimCenterCommon/QGIS/SimCenterQGIS.cmake + the +# from-source QGIS 4 fork. This legacy .pro is kept for reference only; build with CMake. # Specify the path to R2D and common PATH_TO_R2D=../../R2DTool/R2DTool PATH_TO_COMMON=../../SimCenterCommon -PATH_TO_BACKEND=../../OpenSRA +PATH_TO_BACKEND=../../OpenSRABackend #PATH_TO_BACKEND=../OpenSRA_dev # To avoid code copying, include the common SimCenter code @@ -191,7 +192,9 @@ INCLUDEPATH += $$PWD/Utils \ $$PWD/UIWidgets \ $$PWD/JsonWidgets \ $$PWD/ModelViewItems \ + "C:/Program Files (x86)/GnuWin32/include" +LIBS += -L"C:/Program Files (x86)/GnuWin32/lib" -lz # Copies over the examples folder into the build directory #win32 { @@ -227,9 +230,9 @@ INCLUDEPATH += $$PWD/Utils \ #QMAKE_EXTRA_TARGETS += first copyExamples copyBackEnd -#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/'../../../../../../Program Files (x86)/GnuWin32/lib/' -lzlib -#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/'../../../../../../Program Files (x86)/GnuWin32/lib/' -lzlib -#else:unix: LIBS += -L$$PWD/'../../../../../../Program Files (x86)/GnuWin32/lib/' -lzlib +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/'../../../../../../Program Files (x86)/GnuWin32/lib/' -lzlib +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/'../../../../../../Program Files (x86)/GnuWin32/lib/' -lzlib +else:unix: LIBS += -L$$PWD/'../../../../../../Program Files (x86)/GnuWin32/lib/' -lzlib -#INCLUDEPATH += $$PWD/'../../../../../../Program Files (x86)/GnuWin32/include' -#DEPENDPATH += $$PWD/'../../../../../../Program Files (x86)/GnuWin32/include' +INCLUDEPATH += $$PWD/'../../../../../../Program Files (x86)/GnuWin32/include' +DEPENDPATH += $$PWD/'../../../../../../Program Files (x86)/GnuWin32/include' diff --git a/OpenSRA.rc b/OpenSRA.rc new file mode 100644 index 0000000..2a97add --- /dev/null +++ b/OpenSRA.rc @@ -0,0 +1,2 @@ +// Windows application icon (CMake equivalent of the old QMake RC_ICONS line) +IDI_ICON1 ICON "icons/openSRA-icon.ico" diff --git a/OpenSRACommon.pri b/OpenSRACommon.pri index 76b2150..2802f55 100644 --- a/OpenSRACommon.pri +++ b/OpenSRACommon.pri @@ -13,6 +13,8 @@ INCLUDEPATH += $$PATH_TO_R2D \ $$PATH_TO_COMMON/Workflow/GRAPHICS \ $$PATH_TO_COMMON/Workflow/WORKFLOW/ModelViewItems \ $$PATH_TO_COMMON/RandomVariables \ + $$PATH_TO_R2D/systemPerformanceWidgets \ + $$PATH_TO_COMMON/Workflow/WORKFLOW/Utils \ SOURCES += $$PATH_TO_R2D/Tools/CSVReaderWriter.cpp \ diff --git a/OpenSRAPreferences.h b/OpenSRAPreferences.h index 6cb2bbd..a4f3396 100644 --- a/OpenSRAPreferences.h +++ b/OpenSRAPreferences.h @@ -1,5 +1,5 @@ -#ifndef SIMCENTER_PREFERENCES_H -#define SIMCENTER_PREFERENCES_H +#ifndef OPENSRA_PREFERENCES_H +#define OPENSRA_PREFERENCES_H /* ***************************************************************************** Copyright (c) 2016-2017, The Regents of the University of California (Regents). diff --git a/OpenSRAUserPass.h b/OpenSRAUserPass.h index a65d208..82d8b0f 100644 --- a/OpenSRAUserPass.h +++ b/OpenSRAUserPass.h @@ -7,6 +7,8 @@ #include -static QString getArcGISKey(void){return "runtimelite,1000,rud6425635914,none,2K0RJAY3FPJ3R6EJM104";} +// Key intentionally blank: the OpenSRA build is QGIS-based and never calls +// getArcGISKey(); an empty key is sufficient. (Historical ArcGIS key removed.) +static QString getArcGISKey(void){return "";} #endif // OPENSRAUSERPASS_H diff --git a/OpenSRAWorkflow.cmake b/OpenSRAWorkflow.cmake new file mode 100644 index 0000000..4ed4873 --- /dev/null +++ b/OpenSRAWorkflow.cmake @@ -0,0 +1,54 @@ +# OpenSRAWorkflow.cmake +# OpenSRA-specific, SELECTIVE replacement for SimCenterCommon's simcenter_add_workflow(). +# +# Why this exists (do NOT just call simcenter_add_workflow): +# OpenSRA ships its OWN LocalApplication, MainWindowWorkflowApp and GeneralInformationWidget, +# and does NOT do building-damage analysis. SimCenterCommon's simcenter_add_workflow compiles +# EXECUTION/LocalApplication.cpp, WORKFLOW/MainWindowWorkflowApp.cpp and the SIM/*BuildingModel +# sources, which (a) DUPLICATE OpenSRA's own same-named classes (shared include guards -> +# AUTOMOC mocs the wrong header) and (b) drag in building-damage widgets that need InputSheetBM's +# GeneralInformationWidget singleton. That is the root of ~100 of the first-pass build errors. +# +# This module adds ONLY the SimCenterCommon/Workflow sources the original QMake build used +# (OpenSRACommon.pri: the $$PATH_TO_COMMON/Workflow entries). Mirror OpenSRACommon.pri, not R2D. +# +# Usage: +# include(path/to/OpenSRAWorkflow.cmake) +# simcenter_add_opensra_workflow() + +set(OPENSRA_WF_DIR "${CMAKE_CURRENT_LIST_DIR}/../SimCenterCommon/Workflow") + +function(simcenter_add_opensra_workflow target) + set(wf "${OPENSRA_WF_DIR}") + + # Include paths (OpenSRACommon.pri INCLUDEPATH $$PATH_TO_COMMON/Workflow*) + target_include_directories(${target} PRIVATE + "${wf}" + "${wf}/WORKFLOW" + "${wf}/EXECUTION" + "${wf}/GRAPHICS" + "${wf}/WORKFLOW/ModelViewItems" + "${wf}/WORKFLOW/Utils" + ) + + # The exact 7 Workflow sources OpenSRA.pro/OpenSRACommon.pri compiled (+ their headers for AUTOMOC). + # EXECUTION/Application is the base class OpenSRA's own LocalApplication derives from. + target_sources(${target} PRIVATE + "${wf}/WORKFLOW/SimCenterComponentSelection.cpp" + "${wf}/WORKFLOW/CustomizedItemModel.cpp" + "${wf}/EXECUTION/Application.cpp" + "${wf}/GRAPHICS/SimCenterGraphPlot.cpp" + "${wf}/GRAPHICS/qcustomplot.cpp" + "${wf}/WORKFLOW/ModelViewItems/TreeItem.cpp" + "${wf}/WORKFLOW/ModelViewItems/CheckableTreeModel.cpp" + + "${wf}/WORKFLOW/SimCenterComponentSelection.h" + "${wf}/WORKFLOW/AnimatedStackedWidget.h" + "${wf}/WORKFLOW/CustomizedItemModel.h" + "${wf}/EXECUTION/Application.h" + "${wf}/GRAPHICS/SimCenterGraphPlot.h" + "${wf}/GRAPHICS/qcustomplot.h" + "${wf}/WORKFLOW/ModelViewItems/TreeItem.h" + "${wf}/WORKFLOW/ModelViewItems/CheckableTreeModel.h" + ) +endfunction() diff --git a/ResultsWidget.cpp b/ResultsWidget.cpp index 686e75a..bfbce17 100644 --- a/ResultsWidget.cpp +++ b/ResultsWidget.cpp @@ -69,7 +69,7 @@ ResultsWidget::ResultsWidget(QWidget *parent, QGISVisualizationWidget* visWidget mainStackedWidget = new QStackedWidget(this); mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setContentsMargins(5,0,0,0); // Header layout and objects diff --git a/ResultsWidget.h b/ResultsWidget.h index e3eb78c..ef2c502 100644 --- a/ResultsWidget.h +++ b/ResultsWidget.h @@ -91,7 +91,7 @@ private slots: QVBoxLayout* mainLayout; QWidget* resultsPageWidget; - AssetInputDelegate* selectComponentsLineEdit; + AssetInputDelegate* selectComponentsLineEdit = nullptr; QGISVisualizationWidget* theVisualizationWidget; std::unique_ptr theOpenSRAPostProcessor; diff --git a/UIWidgets/AddToRunListWidget.cpp b/UIWidgets/AddToRunListWidget.cpp index 5de62a3..3afb2da 100644 --- a/UIWidgets/AddToRunListWidget.cpp +++ b/UIWidgets/AddToRunListWidget.cpp @@ -60,7 +60,7 @@ AddToRunListWidget::AddToRunListWidget(QWidget* parent) : QWidget(parent) "Combining multiple methods may not be necessarily yield a model that is (log)normally distributed. Please limit to using just one method." ); warningLabel->setWordWrap(true); - warningLayout->setMargin(0); + warningLayout->setContentsMargins(0, 0, 0, 0); warningLayout->addWidget(warningLabel); auto weightLabel = new QLabel("Model Weight:"); @@ -90,7 +90,7 @@ AddToRunListWidget::AddToRunListWidget(QWidget* parent) : QWidget(parent) QHBoxLayout* inputLayout = new QHBoxLayout(); - inputLayout->setMargin(0); + inputLayout->setContentsMargins(0, 0, 0, 0); inputLayout->addWidget(weightLabel); inputLayout->addWidget(weightLineEdit); @@ -108,8 +108,14 @@ AddToRunListWidget::AddToRunListWidget(QWidget* parent) : QWidget(parent) connect(addRunListButton,&QPushButton::clicked, this, [=](){emit addToRunListButtonPressed();}); + // textEdited fires only on user input, not on programmatic setText, so loading + // stored values into the fields cannot echo back into the run list + connect(weightLineEdit, &QLineEdit::textEdited, this, &AddToRunListWidget::inputsEdited); + connect(aleatoryLE, &QLineEdit::textEdited, this, &AddToRunListWidget::inputsEdited); + connect(episLE, &QLineEdit::textEdited, this, &AddToRunListWidget::inputsEdited); + QVBoxLayout* mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addItem(smallVSpacer); diff --git a/UIWidgets/AddToRunListWidget.h b/UIWidgets/AddToRunListWidget.h index 52247a8..ab7afa2 100644 --- a/UIWidgets/AddToRunListWidget.h +++ b/UIWidgets/AddToRunListWidget.h @@ -65,6 +65,9 @@ class AddToRunListWidget : public QWidget signals: void addToRunListButtonPressed(); + // emitted on user edits to the weight/aleatory/epistemic fields + void inputsEdited(); + private: QLineEdit* aleatoryLE = nullptr; diff --git a/UIWidgets/CustomVisualizationWidget.cpp b/UIWidgets/CustomVisualizationWidget.cpp index 80e11b1..710c622 100644 --- a/UIWidgets/CustomVisualizationWidget.cpp +++ b/UIWidgets/CustomVisualizationWidget.cpp @@ -106,7 +106,7 @@ CustomVisualizationWidget::CustomVisualizationWidget(QGISVisualizationWidget* vi theLeftHandWidget->setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Expanding); QVBoxLayout *theLeftHandLayout = new QVBoxLayout(theLeftHandWidget); - theLeftHandLayout->setMargin(0); + theLeftHandLayout->setContentsMargins(0, 0, 0, 0); theLeftHandLayout->addWidget(visSelectBox); @@ -135,7 +135,7 @@ CustomVisualizationWidget::CustomVisualizationWidget(QGISVisualizationWidget* vi auto buttonHandle = new QToolButton(handleLeft); QVBoxLayout *layout = new QVBoxLayout(handleLeft); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); theVizLayout->setHandleWidth(15); @@ -167,7 +167,7 @@ CustomVisualizationWidget::CustomVisualizationWidget(QGISVisualizationWidget* vi auto buttonHandleRight = new QToolButton(handleRight); QVBoxLayout *layoutRight = new QVBoxLayout(handleRight); layoutRight->setSpacing(0); - layoutRight->setMargin(0); + layoutRight->setContentsMargins(0, 0, 0, 0); buttonHandleRight->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); buttonHandleRight->setDown(false); @@ -202,7 +202,17 @@ bool CustomVisualizationWidget::inputFromJSON(QJsonObject &jsonObject) int CustomVisualizationWidget::processResults(QString &filenameResults) { - theOpenSRAPostProcessor->importResults(filenameResults); + // importResults throws QString on a missing/unreadable results file; an exception + // escaping this slot would terminate the app under Qt6 (no error dialog) + try { + theOpenSRAPostProcessor->importResults(filenameResults); + } catch (const QString& msg) { + errorMessage(msg); + return -1; + } catch (const std::exception& e) { + errorMessage(QString("Error importing results: ") + e.what()); + return -1; + } this->resultsShow(true); diff --git a/UIWidgets/DamageMeasureWidget.cpp b/UIWidgets/DamageMeasureWidget.cpp index ce2d0fb..3c494a1 100644 --- a/UIWidgets/DamageMeasureWidget.cpp +++ b/UIWidgets/DamageMeasureWidget.cpp @@ -59,7 +59,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. DamageMeasureWidget::DamageMeasureWidget(QJsonObject mainObj, QWidget *parent): SimCenterAppWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); mainLayout->setContentsMargins(5,0,0,0); @@ -102,8 +102,10 @@ DamageMeasureWidget::DamageMeasureWidget(QJsonObject mainObj, QWidget *parent): vecWidgets.append(newWidget); } - theComponentSelection->setWidth(120); - theComponentSelection->setItemWidthHeight(120,70); + // 150 px so long names like "Shaking Induced Moment on Wells" wrap to + // <= 3 lines of the 16 px bold sidebar font and fit the 70 px item height + theComponentSelection->setWidth(150); + theComponentSelection->setItemWidthHeight(150,70); theComponentSelection->displayComponent(0); diff --git a/UIWidgets/DecisionVariableWidget.cpp b/UIWidgets/DecisionVariableWidget.cpp index f0802bd..023189c 100644 --- a/UIWidgets/DecisionVariableWidget.cpp +++ b/UIWidgets/DecisionVariableWidget.cpp @@ -59,7 +59,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. DecisionVariableWidget::DecisionVariableWidget(QJsonObject mainObj, QWidget *parent): SimCenterAppWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); mainLayout->setContentsMargins(5,0,0,0); @@ -102,8 +102,10 @@ DecisionVariableWidget::DecisionVariableWidget(QJsonObject mainObj, QWidget *par vecWidgets.append(newWidget); } - theComponentSelection->setWidth(120); - theComponentSelection->setItemWidthHeight(120,70); + // 150 px so long names like "Shaking Induced Rupture on Wells" wrap to + // <= 3 lines of the 16 px bold sidebar font and fit the 70 px item height + theComponentSelection->setWidth(150); + theComponentSelection->setItemWidthHeight(150,70); theComponentSelection->displayComponent(0); diff --git a/UIWidgets/EDPLandslideWidget.cpp b/UIWidgets/EDPLandslideWidget.cpp index 53bd539..37dd304 100644 --- a/UIWidgets/EDPLandslideWidget.cpp +++ b/UIWidgets/EDPLandslideWidget.cpp @@ -73,14 +73,14 @@ EDPLandslideWidget::EDPLandslideWidget(QJsonObject obj, QWidget* parent) : SimCe connect(listWidget,&QAbstractItemView::clicked,this,&EDPLandslideWidget::handleListItemSelected); QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setContentsMargins(0,0,0,0); QWidget* mainWidget = new QWidget(); mainWidget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); QVBoxLayout* inputLayout = new QVBoxLayout(mainWidget); - inputLayout->setMargin(0); + inputLayout->setContentsMargins(0, 0, 0, 0); inputLayout->setContentsMargins(5,0,0,0); auto boxWidget = this->getWidgetBox(obj); @@ -213,14 +213,14 @@ QWidget* EDPLandslideWidget::getWidgetBox(QJsonObject& obj) QWidget* yieldAccWidget = new QWidget(); QHBoxLayout* yieldAccLayout = new QHBoxLayout(yieldAccWidget); - yieldAccLayout->setMargin(0); + yieldAccLayout->setContentsMargins(0, 0, 0, 0); yieldAccLayout->addWidget(yieldAccMethodWidget); yieldAccLayout->addWidget(yieldAccParametersWidget); yieldAccLayout->setStretch(0,1); yieldAccLayout->setStretch(1,1); QVBoxLayout* inputLayout = new QVBoxLayout(); - inputLayout->setMargin(0); + inputLayout->setContentsMargins(0, 0, 0, 0); inputLayout->addWidget(methodWidget); inputLayout->addWidget(yieldAccWidget); // inputLayout->setStretch(0,1); @@ -398,7 +398,7 @@ JsonWidget* EDPLandslideWidget::getYieldMethodWidget(const QJsonObject& obj) kyWidget->setObjectName(methodKyStr); QVBoxLayout* kyWidgetLayout = new QVBoxLayout(yieldMethodWidget); - kyWidgetLayout->setMargin(0); + kyWidgetLayout->setContentsMargins(0, 0, 0, 0); kyWidgetLayout->addWidget(widgetLabel); kyWidgetLayout->addWidget(kyWidget); diff --git a/UIWidgets/EngDemandParamWidget.cpp b/UIWidgets/EngDemandParamWidget.cpp index 5748a3c..57011a2 100644 --- a/UIWidgets/EngDemandParamWidget.cpp +++ b/UIWidgets/EngDemandParamWidget.cpp @@ -54,7 +54,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. EngDemandParamWidget::EngDemandParamWidget(QJsonObject mainObj, QWidget *parent) : SimCenterAppWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); mainLayout->setContentsMargins(5,0,0,0); diff --git a/UIWidgets/EngineeringDemandParameterWidget.cpp b/UIWidgets/EngineeringDemandParameterWidget.cpp index ccd7f95..fa15bcd 100644 --- a/UIWidgets/EngineeringDemandParameterWidget.cpp +++ b/UIWidgets/EngineeringDemandParameterWidget.cpp @@ -61,7 +61,7 @@ EngineeringDemandParameterWidget::EngineeringDemandParameterWidget(QJsonObject m this->setObjectName("EngineeringDemandParameter"); QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); mainLayout->setContentsMargins(5,0,0,0); @@ -102,8 +102,10 @@ EngineeringDemandParameterWidget::EngineeringDemandParameterWidget(QJsonObject m vecWidgets.append(newWidget); } - theComponentSelection->setWidth(120); - theComponentSelection->setItemWidthHeight(120,70); + // 150 px to match the DV/DM panel sidebars (long method names wrap to + // <= 3 lines of the 16 px bold sidebar font within the 70 px item height) + theComponentSelection->setWidth(150); + theComponentSelection->setItemWidthHeight(150,70); theComponentSelection->displayComponent(0); diff --git a/UIWidgets/FixedResidualsSamplingWidget.cpp b/UIWidgets/FixedResidualsSamplingWidget.cpp index 3f77d48..5818314 100644 --- a/UIWidgets/FixedResidualsSamplingWidget.cpp +++ b/UIWidgets/FixedResidualsSamplingWidget.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include diff --git a/UIWidgets/GenericModelWidget.cpp b/UIWidgets/GenericModelWidget.cpp index be72400..e61fe77 100644 --- a/UIWidgets/GenericModelWidget.cpp +++ b/UIWidgets/GenericModelWidget.cpp @@ -65,7 +65,7 @@ GenericModelWidget::GenericModelWidget(QString parName, QJsonObject &methodObj, { this->setObjectName("Generic Model Widget of "+parName); verticalLayout = new QVBoxLayout(this); - verticalLayout->setMargin(2); + verticalLayout->setContentsMargins(2, 2, 2, 2); verticalLayout->setSpacing(2); this->makeRVWidget(methodObj); @@ -193,7 +193,7 @@ void GenericModelWidget::makeRVWidget(QJsonObject &methodObj) // model definition table // title & add button QHBoxLayout *titleLayout = new QHBoxLayout(); - //titleLayout->setMargin(10); + //titleLayout->setContentsMargins(10, 10, 10, 10); SectionTitle *title=new SectionTitle(); title->setText(tr("Generic Model Definition")); @@ -365,7 +365,9 @@ bool GenericModelWidget::outputToJSON(QJsonObject &jsonObj) { fileDirInfo.setFile(fileDir); if (!fileDirInfo.exists()) { - return true; + // Never silently drop the model (DistType/TableParams/PathToModelInfo all skipped): + // create the staging folder instead and fall through to the export below. + QDir().mkpath(fileDir); // SimCenterAppWidget::errorMessage("Error: In \"GenericModel.cpp\" - cannot determine path to \"Input\" folder in working dir"); // return false; } @@ -589,7 +591,7 @@ bool GenericModelWidget::inputFromJSON(QJsonObject &jsonObj) { // first try using work_dir/Input as reference #ifdef OpenSRA - filePath = OpenSRAPreferences::getInstance()->getLocalWorkDir() + QDir::separator() + "Input" + filePath; + filePath = OpenSRAPreferences::getInstance()->getLocalWorkDir() + QDir::separator() + "Input" + QDir::separator() + filePath; #else filePath = SimCenterPreferences::getInstance()->getLocalWorkDir() + QDir::separator() + "Input" + filePath; #endif diff --git a/UIWidgets/IntensityMeasureWidget.cpp b/UIWidgets/IntensityMeasureWidget.cpp index 737bc9f..7e42c5a 100644 --- a/UIWidgets/IntensityMeasureWidget.cpp +++ b/UIWidgets/IntensityMeasureWidget.cpp @@ -37,6 +37,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. // Written by: Stevan Gavrilovic #include "IntensityMeasureWidget.h" +#include "QGISVisualizationWidget.h" #include "sectiontitle.h" #include "OpenSHAWidget.h" #include "ShakeMapWidget.h" @@ -84,12 +85,12 @@ IntensityMeasureWidget::IntensityMeasureWidget(QGISVisualizationWidget* visWidge QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(0); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setContentsMargins(5,0,0,0); QHBoxLayout *theHeaderLayout = new QHBoxLayout(); theHeaderLayout->setContentsMargins(0,0,0,0); - theHeaderLayout->setMargin(0); + theHeaderLayout->setContentsMargins(0, 0, 0, 0); theHeaderLayout->setSpacing(0); SectionTitle *label = new SectionTitle(); label->setText(QString("Intensity Measure (IM)")); @@ -116,7 +117,7 @@ IntensityMeasureWidget::IntensityMeasureWidget(QGISVisualizationWidget* visWidge mainPanel = new QStackedWidget(); mainPanel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); mainPanel->setContentsMargins(5,0,0,0); - mainPanel->layout()->setMargin(0); + mainPanel->layout()->setContentsMargins(0, 0, 0, 0); mainPanel->layout()->setSpacing(0); mainPanel->addWidget(openSHA); diff --git a/UIWidgets/LosAngelesPipelineWidget.cpp b/UIWidgets/LosAngelesPipelineWidget.cpp index 61b6989..1eaefd9 100644 --- a/UIWidgets/LosAngelesPipelineWidget.cpp +++ b/UIWidgets/LosAngelesPipelineWidget.cpp @@ -123,7 +123,7 @@ void LosAngelesPipelineWidget::createComponentsBox(void) // QHBoxLayout* regionSitesLayout = new QHBoxLayout(regionLoadWidget); // Insert the widget three rows from the bottom - insertWidgetIntoLayout(regionLoadWidget,3); + //insertWidgetIntoLayout(regionLoadWidget,3); // helper removed in migration; matches sibling pipeline widgets } diff --git a/UIWidgets/MultiComponentDMWidget.cpp b/UIWidgets/MultiComponentDMWidget.cpp index 4fa5278..58bd514 100644 --- a/UIWidgets/MultiComponentDMWidget.cpp +++ b/UIWidgets/MultiComponentDMWidget.cpp @@ -59,7 +59,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. MultiComponentDMWidget::MultiComponentDMWidget(QWidget *parent) : MultiComponentR2D("DamageMeasure",parent) { - theMainLayout->setMargin(0); + theMainLayout->setContentsMargins(0, 0, 0, 0); theMainLayout->setSpacing(0); theMainLayout->setContentsMargins(5,0,0,0); @@ -159,7 +159,7 @@ bool MultiComponentDMWidget::outputAppDataToJSON(QJsonObject &jsonObject) bool MultiComponentDMWidget::inputAppDataFromJSON(QJsonObject &jsonObject) { - auto keyJsonObj = jsonObject[jsonKeyword].toObject(); + auto keyJsonObj = jsonObject[QStringLiteral("DamageMeasure")].toObject(); return this->inputFromJSON(keyJsonObj); } diff --git a/UIWidgets/MultiComponentDVWidget.cpp b/UIWidgets/MultiComponentDVWidget.cpp index 54297aa..0739e7f 100644 --- a/UIWidgets/MultiComponentDVWidget.cpp +++ b/UIWidgets/MultiComponentDVWidget.cpp @@ -58,7 +58,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. MultiComponentDVWidget::MultiComponentDVWidget(QWidget *parent) : MultiComponentR2D("DecisionVariable",parent) { - theMainLayout->setMargin(0); + theMainLayout->setContentsMargins(0, 0, 0, 0); theMainLayout->setSpacing(0); theMainLayout->setContentsMargins(5,0,0,0); @@ -158,7 +158,7 @@ bool MultiComponentDVWidget::outputAppDataToJSON(QJsonObject &jsonObject) bool MultiComponentDVWidget::inputAppDataFromJSON(QJsonObject &jsonObject) { - auto keyJsonObj = jsonObject[jsonKeyword].toObject(); + auto keyJsonObj = jsonObject[QStringLiteral("DecisionVariable")].toObject(); return this->inputFromJSON(keyJsonObj); } diff --git a/UIWidgets/MultiComponentEDPWidget.cpp b/UIWidgets/MultiComponentEDPWidget.cpp index 7072fdf..12e065c 100644 --- a/UIWidgets/MultiComponentEDPWidget.cpp +++ b/UIWidgets/MultiComponentEDPWidget.cpp @@ -59,7 +59,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. MultiComponentEDPWidget::MultiComponentEDPWidget(QWidget *parent) : MultiComponentR2D("EngineeringDemandParameter",parent) { - theMainLayout->setMargin(0); + theMainLayout->setContentsMargins(0, 0, 0, 0); theMainLayout->setSpacing(0); theMainLayout->setContentsMargins(5,0,0,0); @@ -159,7 +159,7 @@ bool MultiComponentEDPWidget::outputAppDataToJSON(QJsonObject &jsonObject) bool MultiComponentEDPWidget::inputAppDataFromJSON(QJsonObject &jsonObject) { - auto keyJsonObj = jsonObject[jsonKeyword].toObject(); + auto keyJsonObj = jsonObject[QStringLiteral("EngineeringDemandParameter")].toObject(); return this->inputFromJSON(keyJsonObj); } diff --git a/UIWidgets/OpenSHAWidget.cpp b/UIWidgets/OpenSHAWidget.cpp index 5de74c8..7aaed75 100644 --- a/UIWidgets/OpenSHAWidget.cpp +++ b/UIWidgets/OpenSHAWidget.cpp @@ -11,7 +11,7 @@ OpenSHAWidget::OpenSHAWidget(QWidget* parent) : SimCenterAppWidget(parent) auto layout = new QVBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); auto mWidget = this->getMainWidget(); diff --git a/UIWidgets/OpenSRAPostProcessor.cpp b/UIWidgets/OpenSRAPostProcessor.cpp index 7a7ba2b..9d99a8d 100644 --- a/UIWidgets/OpenSRAPostProcessor.cpp +++ b/UIWidgets/OpenSRAPostProcessor.cpp @@ -88,7 +88,7 @@ OpenSRAPostProcessor::OpenSRAPostProcessor(QWidget *parent, QGISVisualizationWid listWidget->header()->resizeSections(QHeaderView::ResizeToContents); QVBoxLayout* mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(listWidget); @@ -97,7 +97,9 @@ OpenSRAPostProcessor::OpenSRAPostProcessor(QWidget *parent, QGISVisualizationWid void OpenSRAPostProcessor::handleModifyLegend(void) { - auto res_layer = results_layers.at(0); + auto res_layer = results_layers.value(0); + if (res_layer == nullptr) + return; auto layerId = res_layer->id(); @@ -119,6 +121,12 @@ int OpenSRAPostProcessor::importResultVisuals(const QString& pathToResults) auto mean_layer = results_layers.value(0); + if (mean_layer == nullptr) + { + this->errorMessage("Error: no layers could be loaded from the results file "+pathToResults); + return -1; + } + auto data_provider = dynamic_cast(mean_layer->dataProvider()); if(data_provider == nullptr) @@ -181,10 +189,12 @@ int OpenSRAPostProcessor::importResultVisuals(const QString& pathToResults) QString name; for(auto&& it: results_layers) { - name = it->name(); - if (name == "deformation_polygons_crossed") + // addVectorInGroup() renames every sublayer to "Results"; identify the original + // sublayer via the provider source URI (...|layername=) instead + name = it->source(); + if (name.contains("layername=deformation_polygons_crossed")) it->setOpacity(0.7); - if (name == "caprocks_with_crossings") + if (name.contains("layername=caprocks_with_crossings")) it->setOpacity(0.7); } @@ -229,7 +239,7 @@ void OpenSRAPostProcessor::handleListSelection(const TreeItem* itemSelected) return; } - auto res_layer = results_layers.at(0); + auto res_layer = results_layers.value(0); if(res_layer == nullptr) { diff --git a/UIWidgets/OpenSRAPostProcessor_old.cpp b/UIWidgets/OpenSRAPostProcessor_old.cpp deleted file mode 100644 index 889f1d4..0000000 --- a/UIWidgets/OpenSRAPostProcessor_old.cpp +++ /dev/null @@ -1,1133 +0,0 @@ -/* ***************************************************************************** -Copyright (c) 2016-2021, The Regents of the University of California (Regents). -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. - -REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS -PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, -UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - -*************************************************************************** */ - -// Written by: Stevan Gavrilovic - -#include "CSVReaderWriter.h" -#include "AssetInputWidget.h" -#include "GeneralInformationWidget.h" -#include "MainWindowWorkflowApp.h" -#include "OpenSRAPostProcessor.h" -#include "REmpiricalProbabilityDistribution.h" -#include "TablePrinter.h" -#include "TreeItem.h" -#include "QGISVisualizationWidget.h" -#include "ComponentDatabaseManager.h" -#include "WorkflowAppOpenSRA.h" -#include "EmbeddedMapViewWidget.h" -#include "MutuallyExclusiveListWidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// GIS headers -#include -#include -#include -#include -#include - -using namespace QtCharts; - -OpenSRAPostProcessor::OpenSRAPostProcessor(QWidget *parent, QGISVisualizationWidget* visWidget) : SimCenterAppWidget(parent), theVisualizationWidget(visWidget) -{ - PGVTreeItem = nullptr; - PGDTreeItem = nullptr; - totalTreeItem = nullptr; - defaultItem = nullptr; - thePipelineDb = nullptr; - // The first n columns is information about the component and not results - numInfoCols = 8; - - QVBoxLayout* mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - - // Create a view menu for the dockable windows - mainWidget = new QSplitter(); - mainWidget->setOrientation(Qt::Horizontal); - mainWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); - - listWidget = new MutuallyExclusiveListWidget(this, "Results"); - - connect(listWidget, &MutuallyExclusiveListWidget::itemChecked, this, &OpenSRAPostProcessor::handleListSelection); - connect(listWidget, &MutuallyExclusiveListWidget::clearAll, this, &OpenSRAPostProcessor::clearAll); - connect(theVisualizationWidget,&VisualizationWidget::emitScreenshot,this,&OpenSRAPostProcessor::assemblePDF); - - listWidget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); - - // Create the table that will show the Component information - tableWidget = new QWidget(this); - - PGVResultsTableWidget = new QTableWidget(this); - PGVResultsTableWidget->verticalHeader()->setVisible(false); - PGVResultsTableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); - PGVResultsTableWidget->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents); - PGVResultsTableWidget->setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum); - PGVResultsTableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - PGVResultsTableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - PGVResultsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - PGVResultsTableWidget->setVisible(false); - - PGDResultsTableWidget = new QTableWidget(this); - PGDResultsTableWidget->verticalHeader()->setVisible(false); - PGDResultsTableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); - PGDResultsTableWidget->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents); - PGDResultsTableWidget->setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum); - PGDResultsTableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - PGDResultsTableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - PGDResultsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - PGDResultsTableWidget->setVisible(false); - - // Get the map view widget - auto mapView = theVisualizationWidget->getMapViewWidget("ResultsWidget"); - mapViewSubWidget = std::unique_ptr(mapView); - - // Enable the selection tool - mapViewSubWidget->enableSelectionTool(); - - // Popup stuff - // Once map is set, connect to MapQuickView mouse clicked signal - // connect(mapViewSubWidget.get(), &MapViewSubWidget::mouseClick, theVisualizationWidget, &VisualizationWidget::onMouseClickedGlobal); - - mainWidget->addWidget(mapViewSubWidget.get()); - - QWidget* rightHandWidget = new QWidget(); - QVBoxLayout* rightHandLayout = new QVBoxLayout(rightHandWidget); - rightHandLayout->setMargin(0); - rightHandLayout->setContentsMargins(0, 0, 0, 0); - - rightHandLayout->addWidget(listWidget); - - QPushButton* modifyLegendButton = new QPushButton("Modify Legend",this); - connect(modifyLegendButton, &QPushButton::clicked ,this, &OpenSRAPostProcessor::handleModifyLegend); - - rightHandLayout->addWidget(modifyLegendButton); - - // auto legView = theVisualizationWidget->getLegendView(); - // if(legView != nullptr) - // { - // QLabel* legLabel = new QLabel("Legend",this); - // rightHandLayout->addWidget(legLabel); - // rightHandLayout->addWidget(legView); - // } - - mainWidget->addWidget(rightHandWidget); - - mainLayout->addWidget(mainWidget); - - // The number of header rows in the Pelicun results file - numHeaderRows = 1; - - mainWidget->setStretchFactor(0,2); - - // Now add the splitter handle - // Note: index 0 handle is always hidden, index 1 is between the two widgets - QSplitterHandle *handle = mainWidget->handle(1); - - if(handle == nullptr) - { - qDebug()<<"Error getting the handle"; - return; - } - - auto buttonHandle = new QToolButton(handle); - QVBoxLayout *layout = new QVBoxLayout(handle); - layout->setSpacing(0); - layout->setMargin(0); - - mainWidget->setHandleWidth(15); - - buttonHandle->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); - buttonHandle->setDown(false); - buttonHandle->setAutoRaise(false); - buttonHandle->setCheckable(false); - buttonHandle->setArrowType(Qt::LeftArrow); - buttonHandle->setStyleSheet("QToolButton{border:0px solid}; QToolButton:pressed {border:0px solid}"); - buttonHandle->setIconSize(buttonHandle->size()); - layout->addWidget(buttonHandle); - -} - - -void OpenSRAPostProcessor::handleModifyLegend(void) -{ - -// auto pipelineInputWidget = theVisualizationWidget->getComponentWidget("GASPIPELINES"); - -// if(pipelineInputWidget == nullptr) -// return; - -// auto pipelineLayer = pipelineInputWidget->getSelectedFeatureLayer(); - -// if(pipelineLayer == nullptr) -// return; - -// auto layerId = pipelineLayer->layerId(); - -// theVisualizationWidget->handlePlotColorChange(layerId); -} - - -void OpenSRAPostProcessor::showEvent(QShowEvent *e) -{ - auto mainCanvas = mapViewSubWidget->getMainCanvas(); - - auto mainExtent = mainCanvas->extent(); - - mapViewSubWidget->mapCanvas()->zoomToFeatureExtent(mainExtent); - QWidget::showEvent(e); -} - - -int OpenSRAPostProcessor::importResultVisuals(const QString& pathToResults) -{ - - QDir resultsDir(pathToResults); - - // Get the existing files in the folder - QStringList acceptableFileExtensions = {"*.csv"}; - QStringList existingCSVFiles = resultsDir.entryList(acceptableFileExtensions, QDir::Files); - - if(existingCSVFiles.empty()) - { - errorMessage("The results folder is empty. Did you include DV's in the analysis?"); - return -1; - } - - for(auto&& it : existingCSVFiles) - { - QString pathToFile = pathToResults+ QDir::separator() + it; - - if(it.startsWith("ScenarioTraces")) - { - this->importScenarioTraces(pathToFile); - } - else if(it.startsWith("FaultCrossings")) - { - this->importFaultCrossings(pathToFile); - } - } - - return 0; -} - - -int OpenSRAPostProcessor::importScenarioTraces(const QString& pathToFile) -{ - - if(pathToFile.isEmpty()) - return 0; - - CSVReaderWriter csvTool; - - QString errMsg; - - auto traces = csvTool.parseCSVFile(pathToFile, errMsg); - if(!errMsg.isEmpty()) - { - errorMessage(errMsg); - return -1; - } - - if(traces.size() < 2) - { - statusMessage("No fault traces available."); - return 0; - } - - QgsFields featFields; - featFields.append(QgsField("AssetType", QVariant::String)); - featFields.append(QgsField("TabName", QVariant::String)); - featFields.append(QgsField("SourceIndex", QVariant::String)); - - // Create the pipelines layer - auto mainLayer = theVisualizationWidget->addVectorLayer("linestring","Scenario Faults"); - - if(mainLayer == nullptr) - { - this->errorMessage("Error adding a vector layer"); - return -1; - } - - QList attribFields; - for(int i = 0; idataProvider(); - - mainLayer->startEditing(); - - auto res = pr->addAttributes(attribFields); - - if(!res) - this->errorMessage("Error adding attributes to the layer" + mainLayer->name()); - - mainLayer->updateFields(); // tell the vector layer to fetch changes from the provider - - auto headers = traces.front(); - - auto indexListofTraces = headers.indexOf("ListOfTraces"); - - traces.pop_front(); - - auto numAtrb = attribFields.size(); - - for(auto&& it : traces) - { - auto coordString = it.at(indexListofTraces); - - auto geometry = theVisualizationWidget->getMultilineStringGeometryFromJson(coordString); - - if(geometry.isEmpty()) - { - QString msg ="Error getting the feature geometry for scenario faults layer"; - this->errorMessage(msg); - - return -1; - } - - // create the feature attributes - QgsAttributes featureAttributes(numAtrb); - - featureAttributes[0] = QVariant("SCENARIO_TRACES"); - featureAttributes[1] = QVariant("Scenario Traces"); - featureAttributes[2] = QVariant(it.at(0)); - - - QgsFeature feature; - feature.setFields(featFields); - - feature.setGeometry(geometry); - - feature.setAttributes(featureAttributes); - - if(!feature.isValid()) - return -1; - - auto res = pr->addFeature(feature, QgsFeatureSink::FastInsert); - if(!res) - { - this->errorMessage("Error adding the feature to the layer"); - return -1; - } - } - - mainLayer->commitChanges(true); - mainLayer->updateExtents(); - - QgsLineSymbol* markerSymbol = new QgsLineSymbol(); - - QColor featureColor = QColor(0,0,0,200); - auto weight = 1.0; - - markerSymbol->setWidth(weight); - markerSymbol->setColor(featureColor); - theVisualizationWidget->createSimpleRenderer(markerSymbol,mainLayer); - - - return 0; -} - -int OpenSRAPostProcessor::importFaultCrossings(const QString& pathToFile) -{ - - return 0; -} - - - -void OpenSRAPostProcessor::importResults(const QString& pathToResults) -{ - qDebug() << "OpenSRAPostProcessor: " << pathToResults; - - QString errMsg; - - - // Get the pipelines database - thePipelineDb = ComponentDatabaseManager::getInstance()->getAssetDb("GasNetworkPipelines"); - - if(thePipelineDb == nullptr) - { - errMsg = "Could not get the pipeline database"; - throw errMsg; - } - - QString pathToDv = pathToResults + QDir::separator() + "DV"; - - try { - this->importDVresults(pathToDv); - } - catch (QString err) { - errorMessage(err); - } - - - QString pathToScenarioTraces = pathToResults + QDir::separator() + "IM" + QDir::separator() + "SeismicSource"; - this->importResultVisuals(pathToScenarioTraces); - - - listWidget->expandAll(); -} - - -int OpenSRAPostProcessor::importDVresults(const QString& pathToResults) -{ - - QDir resultsDir(pathToResults); - - QString errMsg; - - // Get the existing files in the folder - QStringList acceptableFileExtensions = {"*.csv"}; - QStringList existingCSVFiles = resultsDir.entryList(acceptableFileExtensions, QDir::Files); - - if(existingCSVFiles.empty()) - { - errMsg = "The results folder is empty. Did you include DV's in the analysis?"; - throw errMsg; - } - - QString PGDResultsSheet; - QString PGVResultsSheet; - QString AllResultsSheet; - - for(auto&& it : existingCSVFiles) - { - if(it.startsWith("RepairRatePGD")) - PGDResultsSheet = it; - else if(it.startsWith("RepairRatePGV")) - PGVResultsSheet = it; - else if(it.startsWith("AllResults")) - AllResultsSheet = it; - } - - // Create the CSV reader/writer tool - CSVReaderWriter csvTool; - - // Vector to hold the attributes - QVector< QgsAttributes > fieldAttributes; - QStringList fieldNames; - - if(!PGVResultsSheet.isEmpty()) - { - RepairRatePGV = csvTool.parseCSVFile(pathToResults + QDir::separator() + PGVResultsSheet,errMsg); - if(!errMsg.isEmpty()) - throw errMsg; - - if(!RepairRatePGV.empty()) - this->processPGVResults(RepairRatePGV,fieldNames,fieldAttributes); - else - { - errMsg = "The PGV results are empty"; - throw errMsg; - } - } - - if(!PGDResultsSheet.isEmpty()) - { - RepairRatePGD = csvTool.parseCSVFile(pathToResults + QDir::separator() + PGDResultsSheet,errMsg); - if(!errMsg.isEmpty()) - throw errMsg; - - if(!RepairRatePGD.empty()) - this->processPGDResults(RepairRatePGD,fieldNames,fieldAttributes); - else - { - errMsg = "The PGD results are empty"; - throw errMsg; - } - } - - if(!AllResultsSheet.isEmpty()) - { - RepairRateAll = csvTool.parseCSVFile(pathToResults + QDir::separator() + AllResultsSheet,errMsg); - if(!errMsg.isEmpty()) - throw errMsg; - - if(!RepairRateAll.empty()) - this->processTotalResults(RepairRateAll,fieldNames,fieldAttributes); - else - { - errMsg = "The total results are empty"; - throw errMsg; - } - } - - // Get the pipelines database - - auto thePipelineDB = ComponentDatabaseManager::getInstance()->getAssetDb("GasNetworkPipelines"); - - if(thePipelineDB == nullptr) - { - QString msg = "Error getting the pipeline database from the input widget!"; - throw msg; - } - - if(thePipelineDB->isEmpty()) - { - QString msg = "Pipeline database is empty"; - throw msg; - } - - auto selFeatLayer = thePipelineDB->getSelectedLayer(); - mapViewSubWidget->setCurrentLayer(selFeatLayer); - -// mapViewSubWidget->addLayerToLegend(selFeatLayer); - - // Starting editing - thePipelineDB->startEditing(); - - auto res = thePipelineDB->addNewComponentAttributes(fieldNames,fieldAttributes,errMsg); - if(!res) - throw errMsg; - - // Commit the changes - thePipelineDB->commitChanges(); - - defaultItem->setState(2); - - listWidget->expandAll(); - - return 0; -} - - -int OpenSRAPostProcessor::processPGVResults(const QVector& DVResults, QStringList& fieldNames, QVector& fieldAttributes) -{ - - auto numRows = DVResults.size(); - - // Check if there is data in the results, and not just the header rows - if(numRows < numHeaderRows) - { - QString msg = "No results to import!"; - throw msg; - } - - auto numHeaderColumns = DVResults.at(0).size(); - - if(numHeaderColumns < numInfoCols) - { - QString msg = "No results to import!"; - throw msg; - } - - if(PGVTreeItem == nullptr) - { - PGVTreeItem = listWidget->addItem("Shaking Induced"); - PGVTreeItem->setIsCheckable(false); - } - - QStringList tableHeadings; - - // Add the ID heading - tableHeadings<addItem(itemStr,PGVTreeItem); - - // Set the header string as a property so I can find the header value later - item->setProperty("HeaderString",headerStr); - } - - PGVResultsTableWidget->setColumnCount(tableHeadings.size()); - PGVResultsTableWidget->setHorizontalHeaderLabels(tableHeadings); - PGVResultsTableWidget->setRowCount(DVResults.size()-numHeaderRows); - - QVector< QgsAttributes > attributes(DVResults.size()-numHeaderRows, QgsAttributes(numHeaderColumns-numInfoCols)); - - // Start at the row where headers end - for(int row = numHeaderRows, count = 0; rowsetItem(row-1,0, tableIDItem); - - auto& rowData = attributes[count]; - - // Populate the table and database with the results - for(int j = 1, k = numInfoCols; ksetItem(row-1,j, tableItem); - - // Add the result to the database - auto value = inputRow.at(k); - - rowData[j-1] = QVariant(value.toDouble()); - } - } - - // Append the new fields and data - - // We do not need the first column that contains the id - tableHeadings.pop_front(); - - fieldNames.append(tableHeadings); - - fieldAttributes.append(attributes); - - return 0; -} - - -int OpenSRAPostProcessor::processPGDResults(const QVector& DVResults, QStringList& fieldNames, QVector& fieldAttributes) -{ - - auto numRows = DVResults.size(); - - // Check if there is data in the results, and not just the header rows - if(numRows < numHeaderRows) - { - QString msg = "No results to import!"; - throw msg; - } - - auto numHeaderColumns = DVResults.at(0).size(); - - if(numHeaderColumns < numInfoCols) - { - QString msg = "No results to import!"; - throw msg; - } - - if(PGDTreeItem == nullptr) - { - PGDTreeItem = listWidget->addItem("Deformation Induced"); - PGDTreeItem->setIsCheckable(false); - } - - QStringList tableHeadings; - - // Add the ID heading - tableHeadings<addItem(itemStr,PGDTreeItem); - - // Set the header string as a property so I can find the header value later - item->setProperty("HeaderString",headerStr); - } - - auto numNewAttributes = numRows-numHeaderRows; - - PGDResultsTableWidget->setColumnCount(tableHeadings.size()); - PGDResultsTableWidget->setHorizontalHeaderLabels(tableHeadings); - PGDResultsTableWidget->setRowCount(numNewAttributes); - - // Start at the row where headers end - for(int row = numHeaderRows, count = 0; rowsetItem(row-1,0, tableIDItem); - - auto& rowData = fieldAttributes[count]; - rowData.reserve(rowData.size()+numNewAttributes); - - // Populate the table and database with the results - for(int j = 1, k = numInfoCols; ksetItem(row-1,j, tableItem); - - // Add the result to the database - auto value = inputRow.at(k); - - rowData.push_back(QVariant(value.toDouble())); - } - } - - // Append the new fields - // We do not need the first column that contains the id - tableHeadings.pop_front(); - - fieldNames.append(tableHeadings); - - return 0; -} - - -int OpenSRAPostProcessor::processTotalResults(const QVector& DVResults, QStringList& fieldNames, QVector& fieldAttributes) -{ - - auto numRows = DVResults.size(); - - // Check if there is data in the results, and not just the header rows - if(numRows < numHeaderRows) - { - QString msg = "No results to import!"; - throw msg; - } - - auto numHeaderColumns = DVResults.at(0).size(); - - if(numHeaderColumns < numInfoCols) - { - QString msg = "No results to import!"; - throw msg; - } - - if(totalTreeItem == nullptr) - { - totalTreeItem = listWidget->addItem("Total Repair Rates"); - totalTreeItem->setIsCheckable(false); - } - - QStringList tableHeadings = DVResults.at(0); - - QString headerStr = "TotalRepairRateForAllDemands"; - - auto indexOfTotals = tableHeadings.indexOf(headerStr,-1); - - if(indexOfTotals == -1) - { - QString msg = "Error getting index to total repairs"; - throw msg; - } - - QString itemStr = "All demands"; - - defaultItem = listWidget->addItem(itemStr,totalTreeItem); - - // Set the header string as a property so I can find the header value later - defaultItem->setProperty("HeaderString",headerStr); - - // Start at the row where headers end - for(int row = numHeaderRows, count = 0; rowtakeScreenShot(); - - return 0; -} - - -void OpenSRAPostProcessor::processResultsSubset(const std::set& selectedComponentIDs) -{ - - if(selectedComponentIDs.empty()) - return; - - if(RepairRatePGD.size() < numHeaderRows) - { - QString msg = "No results to import!"; - throw msg; - } - - if(RepairRatePGD.at(numHeaderRows).isEmpty() || RepairRatePGD.last().isEmpty()) - { - QString msg = "No values in the cells"; - throw msg; - } - - auto firstID = objectToInt(RepairRatePGD.at(numHeaderRows).at(0)); - - auto lastID = objectToInt(RepairRatePGD.last().at(0)); - - QVector DVsubset(&RepairRatePGD[0],&RepairRatePGD[numHeaderRows]); - - for(auto&& id : selectedComponentIDs) - { - // Check that the ID falls within the bounds of the data - if(idlastID) - { - QString msg = "ID " + QString::number(id) + " is out of bounds of the results"; - throw msg; - } - - auto found = false; - for(int i = numHeaderRows; iprocessPGVResults(DVsubset); -} - - -int OpenSRAPostProcessor::assemblePDF(QImage screenShot) -{ - // The printer - QPrinter printer(QPrinter::HighResolution); - printer.setOutputFormat(QPrinter::PdfFormat); - printer.setPaperSize(QPrinter::Letter); - printer.setPageMargins(25.4, 25.4, 25.4, 25.4, QPrinter::Millimeter); - printer.setFullPage(true); - qreal leftMargin, topMargin; - printer.getPageMargins(&leftMargin,&topMargin,nullptr,nullptr,QPrinter::Point); - printer.setOutputFileName(outputFilePath); - - // Create a new document - QTextDocument* document = new QTextDocument(); - QTextCursor cursor(document); - document->setDocumentMargin(25.4); - document->setDefaultFont(QFont("Helvetica")); - - // Define font styles - QTextCharFormat normalFormat; - normalFormat.setFontWeight(QFont::Normal); - - QTextCharFormat titleFormat; - titleFormat.setFontWeight(QFont::Bold); - titleFormat.setFontCapitalization(QFont::AllUppercase); - titleFormat.setFontPointSize(normalFormat.fontPointSize() * 2.0); - - QTextCharFormat captionFormat; - captionFormat.setFontWeight(QFont::Light); - captionFormat.setFontPointSize(normalFormat.fontPointSize() / 2.0); - captionFormat.setFontItalic(true); - - QTextCharFormat boldFormat; - boldFormat.setFontWeight(QFont::Bold); - - QFontMetrics normMetrics(normalFormat.font()); - auto lineSpacing = normMetrics.lineSpacing(); - - // Define alignment formats - QTextBlockFormat alignCenter; - alignCenter.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight) ; - alignCenter.setAlignment(Qt::AlignCenter); - - QTextBlockFormat alignLeft; - alignLeft.setAlignment(Qt::AlignLeft); - alignLeft.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight) ; - - cursor.movePosition(QTextCursor::Start); - - cursor.insertBlock(alignCenter); - - cursor.insertText("\nOpenSRA Tool\n",titleFormat); - - cursor.insertText("Results Summary\n",boldFormat); - - cursor.movePosition( QTextCursor::End ); - - // Ratio of the page width that is printable - auto useablePageWidth = printer.pageRect(QPrinter::Point).width()-(1.5*leftMargin); - - QRect viewPortRect(0, mapViewSubWidget->height(), mapViewSubWidget->width(), mapViewSubWidget->height()); - QImage cropped = screenShot.copy(viewPortRect); - document->addResource(QTextDocument::ImageResource,QUrl("Figure1"),cropped); - QTextImageFormat imageFormatFig1; - imageFormatFig1.setName("Figure1"); - imageFormatFig1.setQuality(600); - imageFormatFig1.setWidth(useablePageWidth); - - cursor.setBlockFormat(alignCenter); - - cursor.insertImage(imageFormatFig1); - - cursor.insertText("Regional map visualization.\n",captionFormat); - - cursor.setBlockFormat(alignLeft); - - TablePrinter prettyTablePrinter; - prettyTablePrinter.printToTable(&cursor, PGVResultsTableWidget,"PGV Results"); - - cursor.insertText("\n\n",normalFormat); - - prettyTablePrinter.printToTable(&cursor, PGDResultsTableWidget,"PGD Results"); - - document->print(&printer); - - return 0; -} - - -void OpenSRAPostProcessor::sortTable(int index) -{ - if(index == 0) - PGVResultsTableWidget->sortByColumn(index,Qt::AscendingOrder); - else - PGVResultsTableWidget->sortByColumn(index,Qt::DescendingOrder); - -} - - -void OpenSRAPostProcessor::clear(void) -{ - RepairRatePGV.clear(); - RepairRatePGD.clear(); - RepairRateAll.clear(); - outputFilePath.clear(); - if(thePipelineDb) - thePipelineDb->clear(); - PGVResultsTableWidget->clear(); - listWidget->clear(); - - if(PGVTreeItem != nullptr) - { - delete PGVTreeItem; - PGVTreeItem = nullptr; - } - if(PGDTreeItem != nullptr) - { - delete PGDTreeItem; - PGDTreeItem = nullptr; - } - if(totalTreeItem != nullptr) - { - delete totalTreeItem; - totalTreeItem = nullptr; - } - if(defaultItem != nullptr) - { - delete defaultItem; - defaultItem = nullptr; - } - - mapViewSubWidget->clear(); -} - - -void OpenSRAPostProcessor::handleListSelection(const TreeItem* itemSelected) -{ - if(itemSelected == nullptr) - return; - - auto headerString = itemSelected->property("HeaderString").toString(); - - if(headerString.isEmpty()) - { - this->errorMessage("Could not find the property "+headerString+" in item "+itemSelected->getName()); - return; - } - - // Get the pipelines database - auto thePipelineDB = ComponentDatabaseManager::getInstance()->getAssetDb("GasNetworkPipelines"); - - if(thePipelineDB == nullptr) - { - this->errorMessage("Error getting the pipeline database from the input widget!"); - return; - } - - if(thePipelineDB->isEmpty()) - { - this->errorMessage("Pipeline database is empty"); - return; - } - - auto selFeatLayer = thePipelineDB->getSelectedLayer(); - if(selFeatLayer == nullptr) - { - this->errorMessage("Layer is a nullptr in handleListSelection"); - return; - } - - // Check to see if that field exists in the layer - auto idx = selFeatLayer->dataProvider()->fieldNameIndex(headerString); - - if(idx == -1) - { - this->errorMessage("Could not find the field "+headerString+" in layer "+selFeatLayer->name()); - return; - } - - auto layerRenderer = selFeatLayer->renderer(); - if(layerRenderer == nullptr) - { - this->errorMessage("No layer renderer available in layer "+selFeatLayer->name()); - return; - } - - // Create a graduated renderer if one does not exist - if(layerRenderer->type().compare("graduatedSymbol") != 0) - { - QVector>classBreaks; - QVector colors; - - classBreaks.append(QPair(0.0, 1E-03)); - classBreaks.append(QPair(1.00E-03, 1.00E-02)); - classBreaks.append(QPair(1.00E-02, 1.00E-01)); - classBreaks.append(QPair(1.00E-01, 1.00E+00)); - classBreaks.append(QPair(1.00E+00, 1.00E+01)); - classBreaks.append(QPair(1.00E+01, 1.00E+10)); - - colors.push_back(Qt::darkBlue); - colors.push_back(QColor(255,255,178)); - colors.push_back(QColor(253,204,92)); - colors.push_back(QColor(253,141,60)); - colors.push_back(QColor(240,59,32)); - colors.push_back(QColor(189,0,38)); - - // createCustomClassBreakRenderer(const QString attrName, const QVector>& classBreaks, const QVector& colors, QgsVectorLayer * vlayer) - theVisualizationWidget->createCustomClassBreakRenderer(headerString,selFeatLayer,Qgis::SymbolType::Line,classBreaks,colors); - } - else if(auto graduatedRender = dynamic_cast(layerRenderer)) - { - graduatedRender->setClassAttribute(headerString); - } - else - { - this->errorMessage("Unrecognized type of layer renderer available in layer "+selFeatLayer->name()); - return; - } - - - theVisualizationWidget->markDirty(); -} - - -void OpenSRAPostProcessor::clearAll(void) -{ - - // Get the pipelines database - auto thePipelineDB = ComponentDatabaseManager::getInstance()->getAssetDb("GasNetworkPipelines"); - - if(thePipelineDB == nullptr) - { - this->errorMessage("Error getting the pipeline database from the input widget!"); - return; - } - - if(thePipelineDB->isEmpty()) - { - this->errorMessage("Pipeline database is empty"); - return; - } - - auto selFeatLayer = thePipelineDB->getSelectedLayer(); - if(selFeatLayer == nullptr) - { - this->errorMessage("Layer is a nullptr in handleListSelection"); - return; - } - - QgsLineSymbol* selectedLayerMarkerSymbol = new QgsLineSymbol(); - - selectedLayerMarkerSymbol->setWidth(2.0); - selectedLayerMarkerSymbol->setColor(Qt::darkBlue); - theVisualizationWidget->createSimpleRenderer(selectedLayerMarkerSymbol,selFeatLayer); - - theVisualizationWidget->markDirty(); -} diff --git a/UIWidgets/OpenSRAPostProcessor_old.h b/UIWidgets/OpenSRAPostProcessor_old.h deleted file mode 100644 index 3d7107f..0000000 --- a/UIWidgets/OpenSRAPostProcessor_old.h +++ /dev/null @@ -1,193 +0,0 @@ -#ifndef OpenSRAPostProcessor_H -#define OpenSRAPostProcessor_H -/* ***************************************************************************** -Copyright (c) 2016-2021, The Regents of the University of California (Regents). -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. - -REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS -PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, -UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - -*************************************************************************** */ - -// Written by: Stevan Gavrilovic - -#include "ComponentDatabase.h" -#include "SimCenterAppWidget.h" -#include "SimCenterMapcanvasWidget.h" - -#include - -#include -#include - -class TreeItem; -class REmpiricalProbabilityDistribution; -class QGISVisualizationWidget; -class MutuallyExclusiveListWidget; - -class QSplitter; -class QTableWidget; -class QGridLayout; -class QLabel; -class QComboBox; - -namespace QtCharts -{ -class QChartView; -class QBarSet; -class QChart; -} - -namespace Esri -{ -namespace ArcGISRuntime -{ -class Map; -class MapGraphicsView; -} -} - - -class OpenSRAPostProcessor : public SimCenterAppWidget -{ - Q_OBJECT - -public: - - OpenSRAPostProcessor(QWidget *parent, QGISVisualizationWidget* visWidget); - - void importResults(const QString& pathToResults); - - int importDVresults(const QString& pathToResults); - - int printToPDF(const QString& outputPath); - - // Function to convert a QString and QVariant to double - // Throws an error exception if conversion fails - template - auto objectToDouble(T obj) - { - // Assume a zero value if the string is empty - if(obj.isNull()) - return 0.0; - - bool OK; - auto val = obj.toDouble(&OK); - - if(!OK) - throw QString("Could not convert the object to a double"); - - return val; - } - - - template - auto objectToInt(T obj) - { - // Assume a zero value if the string is empty - if(obj.isNull()) - return 0; - - bool OK; - auto val = obj.toInt(&OK); - - if(!OK) - throw QString("Could not convert the object to an integer"); - - return val; - } - - void processResultsSubset(const std::set& selectedComponentIDs); - - void clear(void); - -protected: - - void showEvent(QShowEvent *e); - -private slots: - - int assemblePDF(QImage screenShot); - - void sortTable(int index); - - void handleListSelection(const TreeItem* itemSelected); - - void clearAll(void); - - void handleModifyLegend(void); - -private: - - int processPGVResults(const QVector& DVResults, QStringList& fieldNames, QVector& fieldAttributes); - - int processPGDResults(const QVector& DVResults, QStringList& fieldNames, QVector& fieldAttributes); - - int processTotalResults(const QVector& DVResults, QStringList& fieldNames, QVector& fieldAttributes); - - int importResultVisuals(const QString& pathToResults); - int importFaultCrossings(const QString& pathToFile); - int importScenarioTraces(const QString& pathToFile); - - - QVector RepairRatePGD; - QVector RepairRatePGV; - QVector RepairRateAll; - - TreeItem* PGVTreeItem; - TreeItem* PGDTreeItem; - TreeItem* totalTreeItem; - - TreeItem* defaultItem; - - QString outputFilePath; - - QSplitter* mainWidget; - - MutuallyExclusiveListWidget* listWidget; - - QWidget *tableWidget; - QTableWidget* PGVResultsTableWidget; - QTableWidget* PGDResultsTableWidget; - - QGISVisualizationWidget* theVisualizationWidget; - - std::unique_ptr mapViewSubWidget; - - //The number of header rows in the results file - int numHeaderRows; - - // The number of columns that contain component information - int numInfoCols; - - ComponentDatabase* thePipelineDb; -}; - -#endif // OpenSRAPostProcessor_H diff --git a/UIWidgets/OpenSRAPreProcessor.cpp b/UIWidgets/OpenSRAPreProcessor.cpp index 77f1cbe..3da30c5 100644 --- a/UIWidgets/OpenSRAPreProcessor.cpp +++ b/UIWidgets/OpenSRAPreProcessor.cpp @@ -281,9 +281,9 @@ QgsRasterLayer* OpenSRAPreProcessor::loadRaster(const QString& rasterFilePath, c rasterlayer->setOpacity(0.5); - rasterlayer->dataProvider()->setZoomedInResamplingMethod(QgsRasterDataProvider::ResamplingMethod::Bilinear); + rasterlayer->dataProvider()->setZoomedInResamplingMethod(Qgis::RasterResamplingMethod::Bilinear); - rasterlayer->dataProvider()->setZoomedOutResamplingMethod(QgsRasterDataProvider::ResamplingMethod::Bilinear); + rasterlayer->dataProvider()->setZoomedOutResamplingMethod(Qgis::RasterResamplingMethod::Bilinear); rasterlayer->dataProvider()->enableProviderResampling(true); diff --git a/UIWidgets/PipelineDLWidget.cpp b/UIWidgets/PipelineDLWidget.cpp index e8fb6b0..bcbfd65 100644 --- a/UIWidgets/PipelineDLWidget.cpp +++ b/UIWidgets/PipelineDLWidget.cpp @@ -55,7 +55,7 @@ PipelineDLWidget::PipelineDLWidget(QWidget *parent) : MultiComponentR2D("OpenSRA { this->setContentsMargins(0,0,0,0); - theMainLayout->setMargin(0); + theMainLayout->setContentsMargins(0, 0, 0, 0); theMainLayout->setContentsMargins(5,0,0,0); theMainLayout->setSpacing(0); diff --git a/UIWidgets/PipelineNetworkWidget.cpp b/UIWidgets/PipelineNetworkWidget.cpp index c98e1de..4c6ee68 100644 --- a/UIWidgets/PipelineNetworkWidget.cpp +++ b/UIWidgets/PipelineNetworkWidget.cpp @@ -85,13 +85,13 @@ PipelineNetworkWidget::PipelineNetworkWidget(VisualizationWidget* visWidget, QWi { this->setContentsMargins(0,0,0,0); - theMainLayout->setMargin(0); + theMainLayout->setContentsMargins(0, 0, 0, 0); theMainLayout->setContentsMargins(5,0,0,0); theMainLayout->setSpacing(0); QHBoxLayout *theHeaderLayout = new QHBoxLayout(); theHeaderLayout->setContentsMargins(0,0,0,0); - theHeaderLayout->setMargin(0); + theHeaderLayout->setContentsMargins(0, 0, 0, 0); theHeaderLayout->setSpacing(0); SectionTitle *label = new SectionTitle(); label->setText(QString("Infrastructure")); @@ -258,7 +258,11 @@ bool PipelineNetworkWidget::outputToJSON(QJsonObject &jsonObject) QJsonObject compObj; - theCurrInputWidget->outputToJSON(compObj); + if(!theCurrInputWidget->outputToJSON(compObj)) + { + this->errorMessage("Error in the json output of the infrastructure widget "+typeOfInf); + return false; + } QJsonObject assetObj = compObj.value(theCurrInputWidget->getJsonKeyword()).toObject(); @@ -313,7 +317,7 @@ bool PipelineNetworkWidget::outputAppDataToJSON(QJsonObject &jsonObject) return false; } - jsonObject[jsonKeyword] = compObj; + jsonObject[QStringLiteral("GasNetwork")] = compObj; return true; } @@ -322,13 +326,13 @@ bool PipelineNetworkWidget::outputAppDataToJSON(QJsonObject &jsonObject) bool PipelineNetworkWidget::inputAppDataFromJSON(QJsonObject &jsonObject) { - if (!jsonObject.contains(jsonKeyword)) + if (!jsonObject.contains(QStringLiteral("GasNetwork"))) { - this->errorMessage("Missing the json keyword "+jsonKeyword+" in input file"); + this->errorMessage("Missing the json keyword "+QStringLiteral("GasNetwork")+" in input file"); return false; } - auto gasNetworkObj = jsonObject[jsonKeyword].toObject(); + auto gasNetworkObj = jsonObject[QStringLiteral("GasNetwork")].toObject(); QStringList keys = gasNetworkObj.keys(); if (keys.size() == 1) { diff --git a/UIWidgets/RandomVariablesWidget.cpp b/UIWidgets/RandomVariablesWidget.cpp index 4431781..5c2a05c 100644 --- a/UIWidgets/RandomVariablesWidget.cpp +++ b/UIWidgets/RandomVariablesWidget.cpp @@ -62,7 +62,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. RandomVariablesWidget::RandomVariablesWidget(QWidget *parent) : SimCenterAppWidget(parent) { verticalLayout = new QVBoxLayout(this); - verticalLayout->setMargin(2); + verticalLayout->setContentsMargins(2, 2, 2, 2); verticalLayout->setSpacing(2); RVTableHeaders = QStringList({"Name","Description","Source","Distribution Type","Mean or Median","Sigma","CoV","Distribution Min","Distribution Max","From Model"}); @@ -108,7 +108,7 @@ void RandomVariablesWidget::makeRVWidget(void) // title & add button QHBoxLayout *titleLayout = new QHBoxLayout(); - //titleLayout->setMargin(10); + //titleLayout->setContentsMargins(10, 10, 10, 10); SectionTitle *title=new SectionTitle(); title->setText(tr("Input Variables")); @@ -407,8 +407,15 @@ bool RandomVariablesWidget::outputToJSON(QJsonObject &jsonObject) { //auto finalRVPath = pathToRvFile + QDir::separator() + "rvs_input.csv"; //auto finalFixedPath = pathToFixedFile + QDir::separator() + "fixed_input.csv"; - auto finalRVPath = jsonObject["runDir"].toString() + QDir::separator() + "rvs_input.csv"; - auto finalFixedPath = jsonObject["runDir"].toString() + QDir::separator() + "fixed_input.csv"; + // "runDir" exists only in the run flow (assembleInputFile). In File->Save there is no + // staging dir, so emit bare names matching the shipped examples; inputFromJSON resolves + // them against the config's own folder (loadFile sets QDir::currentPath() to it). + // .value() (const) avoids the non-const operator[] inserting a spurious "runDir": null. + const QString runDir = jsonObject.value("runDir").toString(); + auto finalRVPath = runDir.isEmpty() ? QString("rvs_input.csv") + : runDir + QDir::separator() + "rvs_input.csv"; + auto finalFixedPath = runDir.isEmpty() ? QString("fixed_input.csv") + : runDir + QDir::separator() + "fixed_input.csv"; QJsonObject inputParamObj; @@ -846,6 +853,12 @@ bool RandomVariablesWidget::handleLoadVars(const QString& filePath, RVTableView* return false; } + if(param.size() <= indexOfName) + { + this->errorMessage("Error, a short variable row (missing the 'Name' column) in the input file "+filePath); + return false; + } + auto name = param.at(indexOfName); auto vals = getMapFromVals(param); diff --git a/UIWidgets/SourceCharacterizationWidget.cpp b/UIWidgets/SourceCharacterizationWidget.cpp index 0dd49f0..9dfbffa 100644 --- a/UIWidgets/SourceCharacterizationWidget.cpp +++ b/UIWidgets/SourceCharacterizationWidget.cpp @@ -56,7 +56,7 @@ SourceCharacterizationWidget::SourceCharacterizationWidget(QWidget *parent) : SimCenterAppWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); auto sourceLayout = this->getSourceLayout(); diff --git a/UIWidgets/UncertaintyQuantificationWidget.cpp b/UIWidgets/UncertaintyQuantificationWidget.cpp index 63b4cbb..185028a 100644 --- a/UIWidgets/UncertaintyQuantificationWidget.cpp +++ b/UIWidgets/UncertaintyQuantificationWidget.cpp @@ -1,4 +1,4 @@ -/* ***************************************************************************** +/* ***************************************************************************** Copyright (c) 2016-2021, The Regents of the University of California (Regents). All rights reserved. @@ -49,7 +49,7 @@ UncertaintyQuantificationWidget::UncertaintyQuantificationWidget(QWidget *parent : SimCenterAppWidget(parent) { auto layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(5,0,0,0); QHBoxLayout *theHeaderLayout = new QHBoxLayout(); diff --git a/UIWidgets/UserDefinedGroundMotionWidget.cpp b/UIWidgets/UserDefinedGroundMotionWidget.cpp index 53f373e..54c2181 100644 --- a/UIWidgets/UserDefinedGroundMotionWidget.cpp +++ b/UIWidgets/UserDefinedGroundMotionWidget.cpp @@ -150,7 +150,8 @@ bool UserDefinedGroundMotionWidget::inputFromJSON(QJsonObject &jsonObject) } // set the line - if (userDefJsonPbj.contains("PathToGMDataFolder")) + if (userDefJsonPbj.contains("PathToGMDataFolder") + && !userDefJsonPbj["PathToGMDataFolder"].toString().isEmpty()) { auto data_dir = userDefJsonPbj["PathToGMDataFolder"].toString(); diff --git a/UIWidgets/UserInputCPTWidget.cpp b/UIWidgets/UserInputCPTWidget.cpp index 99ab03e..2799fe7 100644 --- a/UIWidgets/UserInputCPTWidget.cpp +++ b/UIWidgets/UserInputCPTWidget.cpp @@ -846,6 +846,11 @@ void UserInputCPTWidget::loadUserCPTData(void) { QStringList& rowStr = data[i]; + // Skip blank/short rows (e.g., a trailing empty line in the CSV) - the + // header-derived indexes would read past the end of the row otherwise + if(rowStr.size() <= qMax(qMax(indexCPTName, indexLon), indexLat)) + continue; + auto stationName = rowStr[indexCPTName]; // Path to station files, e.g., site0.csv diff --git a/UIWidgets/WidgetFactory.cpp b/UIWidgets/WidgetFactory.cpp index 2349392..24aac3f 100644 --- a/UIWidgets/WidgetFactory.cpp +++ b/UIWidgets/WidgetFactory.cpp @@ -1,4 +1,4 @@ -/* ***************************************************************************** +/* ***************************************************************************** Copyright (c) 2016-2017, The Regents of the University of California (Regents). All rights reserved. @@ -125,7 +125,7 @@ QWidget* WidgetFactory::getComboBoxWidget(const QJsonObject& obj, const QString& mainWidget->setObjectName(parentKey); QVBoxLayout* mainLayout = new QVBoxLayout(mainWidget); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(1); JsonComboBox* comboWidget = new JsonComboBox(mainWidget); @@ -291,7 +291,7 @@ QWidget* WidgetFactory::getCheckBoxWidget(const QJsonObject& obj, const QString& checkBoxWidget->setMethodAndParamJsonObj(obj); QHBoxLayout* mainLayout = new QHBoxLayout(mainWidget); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(4); auto text = obj.value("NameToDisplay").toString(); @@ -332,7 +332,7 @@ QLayout* WidgetFactory::getLayoutFromParams(const QJsonObject& params, const QSt else mainLayout = new QHBoxLayout(); - mainLayout->setMargin(4); + mainLayout->setContentsMargins(4, 4, 4, 4); mainLayout->setSpacing(2); // This will create the widgets in a certain order @@ -421,7 +421,7 @@ bool WidgetFactory::addWidgetToLayout(const QJsonObject& paramObj, const QString widgetLabel->setStyleSheet("font-weight: bold; color: black"); QGridLayout* newHLayout = new QGridLayout(); - newHLayout->setMargin(0); + newHLayout->setContentsMargins(0, 0, 0, 0); newHLayout->setSpacing(4); newHLayout->addWidget(widgetLabel,0,0); @@ -442,7 +442,7 @@ bool WidgetFactory::addWidgetToLayout(const QJsonObject& paramObj, const QString QVBoxLayout* newVLayout = new QVBoxLayout(); QGridLayout* newHLayout = new QGridLayout(); - newHLayout->setMargin(0); + newHLayout->setContentsMargins(0, 0, 0, 0); newHLayout->setSpacing(4); newHLayout->addWidget(widgetLabel,0,0); @@ -465,7 +465,7 @@ bool WidgetFactory::addWidgetToLayout(const QJsonObject& paramObj, const QString widgetLabel->setStyleSheet("font-weight: bold; color: black"); QGridLayout* newHLayout = new QGridLayout(); - newHLayout->setMargin(0); + newHLayout->setContentsMargins(0, 0, 0, 0); newHLayout->setSpacing(4); newHLayout->addWidget(widgetLabel,0,0); @@ -490,7 +490,7 @@ bool WidgetFactory::isNestedComboBoxWidget(const QJsonObject& obj) for (option = options.begin(); option != options.end(); ++option) { - auto params = option.value()["Params"].toObject(); + auto params = option.value().toObject().value("Params").toObject(); if(params.size() != 0) return true; diff --git a/WorkflowAppOpenSRA.cpp b/WorkflowAppOpenSRA.cpp index 96ab095..147834e 100644 --- a/WorkflowAppOpenSRA.cpp +++ b/WorkflowAppOpenSRA.cpp @@ -335,7 +335,7 @@ void WorkflowAppOpenSRA::initialize(void) this->setLayout(horizontalLayout); horizontalLayout->setSpacing(0); this->setContentsMargins(0,0,0,0); - horizontalLayout->setMargin(0); + horizontalLayout->setContentsMargins(0, 0, 0, 0); // Create the component selection & add the components to it theComponentSelection = new OpenSRAComponentSelection(this); diff --git a/arcgisruntime.pri b/arcgisruntime.pri deleted file mode 100644 index bbb9f2b..0000000 --- a/arcgisruntime.pri +++ /dev/null @@ -1,27 +0,0 @@ -#------------------------------------------------- -# Copyright 2019 ESRI -# -# All rights reserved under the copyright laws of the United States -# and applicable international laws, treaties, and conventions. -# -# You may freely redistribute and use this sample code, with or -# without modification, provided you include the original copyright -# notice and use restrictions. -# -# See the Sample code usage restrictions document for further information. -#------------------------------------------------- - -contains(QMAKE_HOST.os, Windows):{ - iniPath = $$(ALLUSERSPROFILE)\EsriRuntimeQt\ArcGIS Runtime SDK for Qt $${ARCGIS_RUNTIME_VERSION}.ini -} -else { - userHome = $$system(echo $HOME) - iniPath = $${userHome}/.config/EsriRuntimeQt/ArcGIS Runtime SDK for Qt $${ARCGIS_RUNTIME_VERSION}.ini -} -iniLine = $$cat($${iniPath}, "lines") -dirPath = $$find(iniLine, "InstallDir") -cleanDirPath = $$replace(dirPath, "InstallDir=", "") -priLocation = $$replace(cleanDirPath, '"', "") -!include($$priLocation/sdk/ideintegration/esri_runtime_qt.pri) { - message("Error. Cannot locate ArcGIS Runtime PRI file") -} \ No newline at end of file diff --git a/cmake/FindQCA.cmake b/cmake/FindQCA.cmake new file mode 100644 index 0000000..bf5c8ca --- /dev/null +++ b/cmake/FindQCA.cmake @@ -0,0 +1,101 @@ +# Find QCA (Qt Cryptography Architecture 2+) +# ~~~~~~~~~~~~~~~~ +# When run this will define +# +# QCA_FOUND - system has QCA +# QCA_LIBRARY - the QCA library or framework +# QCA_INCLUDE_DIR - the QCA include directory +# QCA_VERSION_STR - e.g. "2.0.3" +# +# Copyright (c) 2006, Michael Larouche, +# Copyright (c) 2014, Larry Shaffer, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if(QCA_INCLUDE_DIR AND QCA_LIBRARY) + + set(QCA_FOUND TRUE) + +else(QCA_INCLUDE_DIR AND QCA_LIBRARY) + + set(QCA_LIBRARY_NAMES qca-${QT_VERSION_BASE_LOWER} qca2-${QT_VERSION_BASE_LOWER} qca) + set(QCA_PATH_SUFFIXES ${QT_VERSION_BASE_LOWER}/QtCrypto Qca-${QT_VERSION_BASE_LOWER}/QtCrypto qt/Qca-${QT_VERSION_BASE_LOWER}/QtCrypto ${QT_VERSION_BASE_LOWER}/Qca-${QT_VERSION_BASE_LOWER}/QtCrypto QtCrypto) + + find_library(QCA_LIBRARY + NAMES ${QCA_LIBRARY_NAMES} + PATHS + ${LIB_DIR} + $ENV{LIB} + "$ENV{LIB_DIR}" + $ENV{LIB_DIR}/lib + /usr/local/lib + ) + + set(_qca_fw) + if(QCA_LIBRARY MATCHES "/qca.*\\.framework") + string(REGEX REPLACE "^(.*/qca.*\\.framework).*$" "\\1" _qca_fw "${QCA_LIBRARY}") + endif() + + find_path(QCA_INCLUDE_DIR + NAMES QtCrypto + PATHS + "${_qca_fw}/Headers" + ${LIB_DIR}/include + "$ENV{LIB_DIR}/include" + $ENV{INCLUDE} + /usr/local/include + PATH_SUFFIXES ${QCA_PATH_SUFFIXES} + ) + + if(QCA_LIBRARY AND QCA_INCLUDE_DIR) + set(QCA_FOUND TRUE) + endif() + +endif(QCA_INCLUDE_DIR AND QCA_LIBRARY) + +if(NOT QCA_FOUND) + + if(QCA_FIND_REQUIRED) + message(FATAL_ERROR "Could not find QCA") + else() + message(STATUS "Could not find QCA") + endif() + +else(NOT QCA_FOUND) + + # Check version is valid (>= 2.0.3) + # find_package(QCA 2.0.3) works with 2.1.0+, which has a QcaConfigVersion.cmake, but 2.0.3 does not + + # qca_version.h header only available with 2.1.0+ + set(_qca_version_h "${QCA_INCLUDE_DIR}/qca_version.h") + if(EXISTS "${_qca_version_h}") + file(STRINGS "${_qca_version_h}" _qca_version_str REGEX "^.*QCA_VERSION_STR +\"[^\"]+\".*$") + string(REGEX REPLACE "^.*QCA_VERSION_STR +\"([^\"]+)\".*$" "\\1" QCA_VERSION_STR "${_qca_version_str}") + else() + # qca_core.h contains hexadecimal version in <= 2.0.3 + set(_qca_core_h "${QCA_INCLUDE_DIR}/qca_core.h") + if(EXISTS "${_qca_core_h}") + file(STRINGS "${_qca_core_h}" _qca_version_str REGEX "^#define +QCA_VERSION +0x[0-9a-fA-F]+.*") + string(REGEX REPLACE "^#define +QCA_VERSION +0x([0-9a-fA-F]+)$" "\\1" _qca_version_int "${_qca_version_str}") + if("${_qca_version_int}" STREQUAL "020003") + set(QCA_VERSION_STR "2.0.3") + endif() + endif() + endif() + + if(NOT QCA_VERSION_STR) + set(QCA_FOUND FALSE) + if(QCA_FIND_REQUIRED) + message(FATAL_ERROR "Could not find QCA >= 2.0.3") + else() + message(STATUS "Could not find QCA >= 2.0.3") + endif() + else() + if(NOT QCA_FIND_QUIETLY) + message(STATUS "Found QCA: ${QCA_LIBRARY} (${QCA_VERSION_STR})") + endif() + endif() + +endif(NOT QCA_FOUND) diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..6b116f1 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,41 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout +from conan.tools.files import copy +import os + +# Conan v2 recipe for OpenSRA, mirroring R2DTool/conanfile2.py. +# Provides jansson, zlib, nlohmann_json and (non-Linux) libcurl via CMakeDeps so the +# top-level CMakeLists.txt find_package() calls resolve. QCA and the QGIS libraries are +# NOT Conan deps here: QCA is located by cmake/FindQCA.cmake, and the qgis_* libraries are +# linked directly from the user-supplied QGIS_LIB_DIR (see CMakeLists.txt). + +class OpenSRA_Conan(ConanFile): + name = "OpenSRA" + version = "1.0.0" + license = "BSD-3-Clause" + author = "NHERI SimCenter" + url = "https://github.com/NHERI-SimCenter/OpenSRA" + description = "OpenSRA - Open-source Seismic Risk Analysis frontend" + settings = "os", "arch", "compiler", "build_type" + + def requirements(self): + self.requires("jansson/2.13.1") + self.requires("zlib/1.3.1") + self.requires("nlohmann_json/3.12.0") + if self.settings.os != "Linux": + self.requires("libcurl/8.12.1") + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + if self.settings.os == "Windows": + # Stage dependency DLLs next to the executable (build/) + bindir = os.path.join(self.build_folder, str(self.settings.build_type)) + for dep in self.dependencies.values(): + if dep.cpp_info.bindirs: + source_dir = dep.cpp_info.bindirs[0] + if os.path.exists(source_dir): + copy(self, "*.dll", source_dir, bindir) diff --git a/main.cpp b/main.cpp index a156a92..132a666 100644 --- a/main.cpp +++ b/main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,7 @@ void customMessageOutput(QtMsgType type, const QMessageLogContext &context, cons QFile outFile(logFilePath); outFile.open(QIODevice::WriteOnly | QIODevice::Append); QTextStream ts(&outFile); - ts << txt << endl; + ts << txt << Qt::endl; outFile.close(); } else { fprintf(stderr, "%s %s: %s (%s:%u, %s)\n", formattedTimeMsg.constData(), logLevelMsg.constData(), localMsg.constData(), context.file, context.line, context.function); @@ -84,6 +85,21 @@ int main(int argc, char *argv[]) QgsApplication a( argc, argv, true ); + // Packaged layout ships QGIS providers/resources next to the exe. When no explicit + // QGIS_PREFIX_PATH is set (app launched directly, not via RunOpenSRA-Portable.cmd), + // point QGIS at the exe directory so providers load R2D-style on a double-click. + if (qEnvironmentVariableIsEmpty("QGIS_PREFIX_PATH")) + QgsApplication::setPrefixPath(QCoreApplication::applicationDirPath(), true); + + // GUI-side PROJ (proj_9.dll) has no valid data dir in the packaged layout — QGIS + // only bundles proj data on macOS — so grid-based datum transforms would silently + // degrade. Point PROJ_DATA at the shipped share/proj when present (set before + // initQgis() runs; never overrides a user-provided PROJ_DATA/PROJ_LIB). + if (qEnvironmentVariableIsEmpty("PROJ_DATA") && qEnvironmentVariableIsEmpty("PROJ_LIB")) { + const QString projData = QCoreApplication::applicationDirPath() + "/share/proj"; + if (QFileInfo::exists(projData + "/proj.db")) + qputenv("PROJ_DATA", QDir::toNativeSeparators(projData).toLocal8Bit()); + } auto prefs = OpenSRAPreferences::getInstance(); diff --git a/msvc_fix.h b/msvc_fix.h new file mode 100644 index 0000000..81fd5e1 --- /dev/null +++ b/msvc_fix.h @@ -0,0 +1,23 @@ +#pragma once + +#if defined(_WIN32) + // Only process these lines if we are in a C++ file + #ifdef __cplusplus + #include + // Use a more specific guard to avoid colliding with + // the Windows SDK's definition of byte + #ifndef _BYTE_DEFINED + #define _BYTE_DEFINED + #define _RPCNDR_H_ + using byte = unsigned char; + #endif + #endif + + // These macros are safe for both C and C++ + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif +#endif