diff --git a/qt6-after/qt6-migrated-legatus/Lts/Services/CentralEffectSimulation/Plugins/Explosions/expand_polygon.cpp b/qt6-after/qt6-migrated-legatus/Lts/Services/CentralEffectSimulation/Plugins/Explosions/expand_polygon.cpp index ea0920103..c1aed9abb 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Services/CentralEffectSimulation/Plugins/Explosions/expand_polygon.cpp +++ b/qt6-after/qt6-migrated-legatus/Lts/Services/CentralEffectSimulation/Plugins/Explosions/expand_polygon.cpp @@ -11,8 +11,8 @@ #include -#include -#include +#include +#include namespace CesExplosions { diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/layer_factory.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/layer_factory.h index 1254621ac..7f63fabeb 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/layer_factory.h +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/layer_factory.h @@ -12,7 +12,7 @@ #include "situation_map_types.h" #include -#include // This file is created by RME, not ESRI +#include // This file is created by RME, not ESRI // Qt diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/map_initializer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/map_initializer.cpp index 00ca18673..2da6bc731 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/map_initializer.cpp +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/map_initializer.cpp @@ -18,7 +18,7 @@ #include #include -#include // This file is created by RME, not ESRI +#include // This file is created by RME, not ESRI #include #include diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.cpp index 5a5a210d9..8cafdc71f 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.cpp +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.cpp @@ -12,13 +12,13 @@ // Esri -#include // for LicenseStatus -#include -#include -#include +#include // for LicenseStatus +#include +#include +#include -#include // This file is created by RME, not ESRI +#include // This file is created by RME, not ESRI #if ACRGIS_VERSION >= 0x640600 // 100.6.0 / Update 6 #define HAS_SURFACEPLACEMENT_RELATIVETOSCENE diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.h index 174412c6a..2a5ef816b 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.h +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Map/situation_map_utils.h @@ -13,8 +13,8 @@ // arcgis -#include // SurfacePlacement -#include +#include // SurfacePlacement +#include // Qt diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Symbology/symbol_dictionary.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Symbology/symbol_dictionary.h index d6282af9b..c15b6a6f1 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Symbology/symbol_dictionary.h +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Symbology/symbol_dictionary.h @@ -26,7 +26,7 @@ class QValidator; #ifdef BUILD_ARCGIS -# include // This file is created by RME, not ESRI +# include // This file is created by RME, not ESRI (300: flat include layout, no arcgis/ subdir) #if ACRGIS_VERSION >= 0x640600 // 100.6.0 / Update 6 //# define USE_ARCADE_SYMBOLOGY diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.cpp index 6cb6e4770..0dab0a53a 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.cpp +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.cpp @@ -18,8 +18,9 @@ // arcgis -#include #include +#include // Esri::ArcGISRuntime::LoadStatus (300: enum class; Loadable.h only forward-declares it) +#include // Esri::ArcGISRuntime::Error (complete type; 300 dropped Error::code()) // Qt @@ -31,6 +32,9 @@ #include #include #include +#include +#include +#include namespace AdvancedSymbology @@ -159,7 +163,7 @@ QString BaseSymbolItem::symbolSet() { if (m_symbolSet.isEmpty()) { - SidcDefinitions::SymbolSet symbolSet = SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt() // Qt6: leftRef removed); + SidcDefinitions::SymbolSet symbolSet = SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt()); // Qt6: leftRef(2) removed m_symbolSet = QString(QStringLiteral("%1 - ")).arg(static_cast(symbolSet)) + AdvancedSymbology::SidcDefinitions::toString(symbolSet); } return m_symbolSet; @@ -167,7 +171,7 @@ QString BaseSymbolItem::symbolSet() int BaseSymbolItem::symbolSetAsInt() { - return static_cast(SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt() // Qt6: leftRef removed)); + return static_cast(SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt())); // Qt6: leftRef(2) removed } @@ -222,23 +226,32 @@ QImage BaseSymbolItem::getSearchSwatch() // qCritical() << " SEARCH: id=" << m_id << " create swatch now"; // qCritical() << " SEARCH: id=" << m_esriSearch.m_esriSymbol->toJson(); - connect(m_esriSearch.m_esriSymbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted, - this, &BaseSymbolItem::onEsriSearchSymbolSwatchCompleted, Qt::UniqueConnection); - - const Esri::ArcGISRuntime::TaskWatcher &tw = m_esriSearch.m_esriSymbol->createSwatch(SWATCH_SIZE, SWATCH_SIZE, 1); - if (tw.isValid()) - m_esriSearch.m_esriTaskUuid = tw.taskId(); + // ArcGIS 300: no TaskWatcher / createSwatchCompleted — createSwatchAsync() returns a + // QFuture; a per-request QFutureWatcher delivers the QImage on the main thread. + const QFuture fut = m_esriSearch.m_esriSymbol->createSwatchAsync(SWATCH_SIZE, SWATCH_SIZE, 1); + if (fut.isValid()) + { + m_esriSearch.m_esriTaskUuid = QUuid::createUuid(); // in-flight guard (was the 100.x task id) + QFutureWatcher *watcher = new QFutureWatcher(this); + watcher->setFuture(fut); + // 300/Qt6: the 'result' signal is name-collision-ambiguous with the result() getter (QOverload + // can't disambiguate); connect to the unambiguous finished() signal and read the value via the + // result() getter (the future is finished when finished() fires). + connect(watcher, &QFutureWatcher::finished, watcher, + [this, watcher] { + onEsriSearchSymbolSwatchCompleted(watcher->result()); + watcher->deleteLater(); + }); + } else - qCritical() << " SEARCH: id=" << m_id << " taskId invalid"; + qCritical() << " SEARCH: id=" << m_id << " createSwatchAsync future invalid"; } return m_esriSearch.m_esriSwatch; } -void BaseSymbolItem::onEsriSearchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image) +void BaseSymbolItem::onEsriSearchSymbolSwatchCompleted(const QImage &image) { - if (m_esriSearch.m_esriTaskUuid != taskId) - return; - + // ArcGIS 300: the dedicated QFutureWatcher makes the (removed) taskId stale-check unnecessary. m_esriSearch.m_esriSwatch = image; emit dataChanged(m_id); @@ -251,9 +264,6 @@ QImage BaseSymbolItem::getFetchSwatch() const if (m_esriFetch.m_esriSwatch.isNull() && m_esriFetch.m_esriFetchTaskIdUuid.isNull()) { - connect(m_esriDictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::fetchSymbolCompleted, - this, &BaseSymbolItem::onEsriFetchCompleted, Qt::UniqueConnection); - // from key8 to sidc (20 digits) const QString &key8 = m_symbolProperties.key().left(8); @@ -272,39 +282,57 @@ QImage BaseSymbolItem::getFetchSwatch() const const QVariantMap keys({{QStringLiteral("sidc"), m_esriFetch.m_sidc}, // SidcCodec::decodeRawSidc ; set sidc of symbol in case of MIL2525 {QStringLiteral("sic"), m_esriFetch.m_sidc}}); // SidcCodec::decodeRawSidc , set sidc of symbol in case of APP6 - const Esri::ArcGISRuntime::TaskWatcher &tw = m_esriDictionary->fetchSymbol(keys); - if (tw.isValid()) - m_esriFetch.m_esriFetchTaskIdUuid = tw.taskId(); + // ArcGIS 300: no TaskWatcher / fetchSymbolCompleted — fetchSymbolAsync() returns a + // QFuture; a per-request QFutureWatcher delivers the Symbol* on the main thread. + const QFuture fut = m_esriDictionary->fetchSymbolAsync(keys); + if (fut.isValid()) + { + m_esriFetch.m_esriFetchTaskIdUuid = QUuid::createUuid(); // in-flight guard (was the 100.x task id) + + BaseSymbolItem *const self = const_cast(this); // this method is const + QFutureWatcher *watcher = new QFutureWatcher(self); + watcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the Symbol* via the result() getter (see above). + connect(watcher, &QFutureWatcher::finished, watcher, + [self, watcher] { + self->onEsriFetchCompleted(watcher->result()); + watcher->deleteLater(); + }); + } else - qCritical() << " FETCH: id=" << m_id << " m_esriFetchTaskIdUuid invalid"; + qCritical() << " FETCH: id=" << m_id << " fetchSymbolAsync future invalid"; } return m_esriFetch.m_esriSwatch; } -void BaseSymbolItem::onEsriFetchCompleted(const QUuid &taskId, Esri::ArcGISRuntime::Symbol *symbol) +void BaseSymbolItem::onEsriFetchCompleted(Esri::ArcGISRuntime::Symbol *symbol) { - if (m_esriFetch.m_esriFetchTaskIdUuid != taskId) - return; + // ArcGIS 300: the dedicated QFutureWatcher makes the (removed) taskId stale-check unnecessary. // save intermediate result: m_esriFetch.m_esriSymbol = symbol; - // start creating swatch of fetched symbol: - connect(m_esriFetch.m_esriSymbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted, - this, &BaseSymbolItem::onEsriFetchSymbolSwatchCompleted, Qt::UniqueConnection); - - const Esri::ArcGISRuntime::TaskWatcher &tw = m_esriFetch.m_esriSymbol->createSwatch(SWATCH_SIZE, SWATCH_SIZE, 1); - if (tw.isValid()) - m_esriFetch.m_esriSwatchTaskUuid = tw.taskId(); + // start creating swatch of fetched symbol (createSwatchAsync -> QFuture): + const QFuture fut = m_esriFetch.m_esriSymbol->createSwatchAsync(SWATCH_SIZE, SWATCH_SIZE, 1); + if (fut.isValid()) + { + m_esriFetch.m_esriSwatchTaskUuid = QUuid::createUuid(); // in-flight guard (was the 100.x task id) + QFutureWatcher *watcher = new QFutureWatcher(this); + watcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the QImage via the result() getter (see above). + connect(watcher, &QFutureWatcher::finished, watcher, + [this, watcher] { + onEsriFetchSymbolSwatchCompleted(watcher->result()); + watcher->deleteLater(); + }); + } else - qCritical() << " FETCH: id=" << m_id << " taskId invalid"; + qCritical() << " FETCH: id=" << m_id << " createSwatchAsync future invalid"; } -void BaseSymbolItem::onEsriFetchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image) +void BaseSymbolItem::onEsriFetchSymbolSwatchCompleted(const QImage &image) { - if (m_esriFetch.m_esriSwatchTaskUuid != taskId) - return; - + // ArcGIS 300: the dedicated QFutureWatcher makes the (removed) taskId stale-check unnecessary. m_esriFetch.m_esriSwatch = image; // qCritical() << " FETCH: id=" << m_id << " image " << image.size(); @@ -426,7 +454,7 @@ void BaseSymbolModel::onLoadStatusChanged(Esri::ArcGISRuntime::LoadStatus loadSt void BaseSymbolModel::onErrorDictionary(const Esri::ArcGISRuntime::Error &error) { FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " - << " code=" << error.code() + << " code=" << error.arcGISServerErrorCode().value_or(-1) // 300: Error::code() removed << ", domain=" << static_cast(error.domain()) << ", extendedErrorType=" << static_cast(error.extendedErrorType()) << ", message='" << error.message() << "'" @@ -456,13 +484,15 @@ void BaseSymbolModel::initModel(SymbolDictionary *symbolDictionary, // spend time to create (expensive) Esri::ArcGISRuntime::Symbol? #ifdef LTS_DEBUG - Esri::ArcGISRuntime::Symbol *esriSymbol = nullptr; + Esri::ArcGISRuntime::Symbol *esriSymbol = nullptr; // 300: filled asynchronously (see below) const bool showAll = QGuiApplication::keyboardModifiers().testFlag(Qt::KeyboardModifier::ControlModifier); - if (showAll) - esriSymbol = searchResult.symbol(this); // symbol() has to be called from mainThread, is expensive / time consuming + // 300: symbol() (const, sync, mainThread) is gone; fetchSymbolAsync() is non-const + async. + // When showAll, the Symbol is fetched after the item exists and attached via + // setEsriSearchSymbol() when the future completes. #else // release mode: save time, won´t be visible Esri::ArcGISRuntime::Symbol *esriSymbol = nullptr; + const bool showAll = false; #endif // LTS_DEBUG @@ -470,6 +500,29 @@ void BaseSymbolModel::initModel(SymbolDictionary *symbolDictionary, bsi->setSymbolDictionary(symbolDictionary); +#ifdef LTS_DEBUG + // 300: fetch the Symbol asynchronously and attach it to the item when the future completes. + // fetchSymbolAsync() is non-const; copy the (const) search result to call it. + if (showAll) + { + Esri::ArcGISRuntime::SymbolStyleSearchResult sr(searchResult); + QFuture fut = sr.fetchSymbolAsync(this); + if (fut.isValid()) + { + QFutureWatcher *watcher = new QFutureWatcher(this); + watcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the Symbol* via the result() getter (see above). + connect(watcher, &QFutureWatcher::finished, watcher, + [bsi, watcher] { + Esri::ArcGISRuntime::Symbol *symbol = watcher->result(); + if (symbol) + bsi->setEsriSearchSymbol(symbol); + watcher->deleteLater(); + }); + } + } +#endif // LTS_DEBUG + bsi->m_symbolProperties.setTags(searchResult.tags()); bsi->m_symbolProperties.setName(searchResult.name()); bsi->m_symbolProperties.setSymbolClass(searchResult.symbolClass()); diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.h index 6ec33d2e9..c87bd7deb 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.h +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.h @@ -141,11 +141,11 @@ Q_SIGNALS: public Q_SLOTS: // SEARCH_ICON: - void onEsriSearchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image); + void onEsriSearchSymbolSwatchCompleted(const QImage &image); // FETCH_ICON: - void onEsriFetchCompleted(const QUuid &taskId, Esri::ArcGISRuntime::Symbol *symbol); - void onEsriFetchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image); + void onEsriFetchCompleted(Esri::ArcGISRuntime::Symbol *symbol); + void onEsriFetchSymbolSwatchCompleted(const QImage &image); }; //------------------------------------------------------------------------------ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.cpp index 60ebe3994..2119396d5 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.cpp +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.cpp @@ -14,7 +14,6 @@ // ESRI -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #define CREATE_ESRI_SYMBOL_ON_DEMAND // save time on startup // REGG-3483 - EsriDictionaryProcessor::syncTableWithinMainThread verzögert Applikations-Start um mehrere Sekunden @@ -46,6 +46,18 @@ EsriDictionaryProcessor::EsriDictionaryProcessor(SymbologyType symbologyType) : m_logger(getLogger("EsriDictionaryProcessor")), m_baseSymbolCodeToSymbolPropertyTable(symbologyType, SidcCodec::SidcType::Base) { + // ArcGIS 300: the 100.x searchSymbolsCompleted signal / TaskWatcher is gone. + // searchSymbolsAsync() returns a QFuture; m_searchSymbolsWatcher delivers its result on + // this (main) thread via its 'result' signal -> onSearchSymbolsCompletedPropertyTable(). + // 300/Qt6: the 'result' signal is name-collision-ambiguous with the result() getter (QOverload can't + // disambiguate); connect to the unambiguous finished() signal and read the value via the result() + // getter (the future is finished when finished() fires). + connect(&m_searchSymbolsWatcher, + &QFutureWatcher::finished, + this, [this] { + onSearchSymbolsCompletedPropertyTable(m_searchSymbolsWatcher.result()); + }); + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " ThreadId=" << QThread::currentThreadId(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } @@ -94,16 +106,16 @@ void EsriDictionaryProcessor::searchSymbols() // search for all results (and store them in member): // - connect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::searchSymbolsCompleted, - this, &EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable, Qt::UniqueConnection); - Esri::ArcGISRuntime::SymbolStyleSearchParameters sssp; - Esri::ArcGISRuntime::TaskWatcher tw = m_esri_dictionary->searchSymbols(sssp); - if (tw.isValid()) - DEBUG_LOG(m_logger) << LOGFUNCTION << " TaskWatcher valid: ssTaskId=" << tw.taskId().toString(); + // ArcGIS 300: no TaskWatcher / searchSymbolsCompleted — searchSymbolsAsync() returns a + // QFuture; m_searchSymbolsWatcher (connected in the constructor) delivers the result on the + // main thread via its 'result' signal -> onSearchSymbolsCompletedPropertyTable(). + m_searchSymbolsWatcher.setFuture(m_esri_dictionary->searchSymbolsAsync(sssp)); + if (m_searchSymbolsWatcher.future().isValid()) + DEBUG_LOG(m_logger) << LOGFUNCTION << " searchSymbolsAsync future valid"; else - ERROR_LOG(m_logger) << LOGFUNCTION << " TaskWatcher invalid" << " ThreadId=" << QThread::currentThreadId(); + ERROR_LOG(m_logger) << LOGFUNCTION << " searchSymbolsAsync future invalid" << " ThreadId=" << QThread::currentThreadId(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; @@ -115,7 +127,8 @@ void EsriDictionaryProcessor::startBaseSymbolCodeToPropertyTable() // A-sync: // (slot is a pure virtual method) - m_baseSymbolCodeToSymbolPropertyTableWatcher = QtConcurrent::run(this, &EsriDictionaryProcessor::processBaseSymbolCodeToPropertyTable); + // Qt6: QtConcurrent::run(context, &method) is gone; pass the member fn first, then the receiver. + m_baseSymbolCodeToSymbolPropertyTableWatcher = QtConcurrent::run(&EsriDictionaryProcessor::processBaseSymbolCodeToPropertyTable, this); // Sync: // processBaseSymbolCodeToPropertyTable(); @@ -129,7 +142,8 @@ void EsriDictionaryProcessor::startModifierTable() // A-sync: // (slot is a pure virtual method) - m_modifierTableWatcher = QtConcurrent::run(this, &EsriDictionaryProcessor::processModifierTable); + // Qt6: QtConcurrent::run(context, &method) is gone; pass the member fn first, then the receiver. + m_modifierTableWatcher = QtConcurrent::run(&EsriDictionaryProcessor::processModifierTable, this); // Sync: // processModifierTable(); @@ -184,9 +198,9 @@ bool EsriDictionaryProcessor::allJobsDone() const // Job n } -void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(QUuid, Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults) +void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults) { - // ssTaskId can be ignored during initializing phase: + // ArcGIS 300: the 100.x ssTaskId (task uuid) no longer exists; the future is 1:1 with this call. TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // save: @@ -196,9 +210,7 @@ void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(QUuid, Esri: // cleanup: searchResults->deleteLater(); - // disconnect: - disconnect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::searchSymbolsCompleted, - this, &EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable); + // (no disconnect needed: 300 has no dict signal; the QFutureWatcher fires once per setFuture) // method 'symbol' has to be called (created) within mainThread: syncTableWithinMainThread(); @@ -330,45 +342,91 @@ QSharedPointer EsriDictionaryProcessor::symbolPropertyImage(QShare if (eSym.swatchImage.isNull()) { + // ArcGIS 300: createSwatch needs a Symbol. With CREATE_ESRI_SYMBOL_ON_DEMAND the Symbol is + // fetched on demand; fetchSymbolAsync() (100.x: symbol()) is async, so fetch it first and only + // then create the swatch. When the Symbol is already available, create the swatch directly. + // Both paths funnel into startSwatchCreation() (the createSwatchAsync part, unchanged from the + // 100.x logic): a per-request QFutureWatcher delivers the QImage on this (main) thread -> + // onEsriSymbolSwatchCompleted(), correlated with our own QUuid (the SDK no longer issues one). + EsriDictionaryProcessor *const self = const_cast(this); // this method is const + auto startSwatchCreation = [self, symResult, originalKey]() { + auto it = self->m_originalKeyToEsriSymbol.find(originalKey); + if (it == self->m_originalKeyToEsriSymbol.end()) + return; + const EsriSymbol &eSym = it.value(); +#ifdef USE_ARCADE_SYMBOLOGY + QFuture fut; + auto it2 = self->m_baseSymbolCodeToSymbolPropertyTable.find(originalKey); + if (it2 != self->m_baseSymbolCodeToSymbolPropertyTable.end() && (it2->geometryType() == GeometryType::Polygon) ) + // area/Polygon -> request rectangle, otherwise quadratic swatch: + fut = eSym.esriSymbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); + else + fut = eSym.esriSymbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); +#else + QFuture fut = eSym.esriSymbol->createSwatchAsync(CREATE_SWATCH_SCREEN_SCALE); +#endif // USE_ARCADE_SYMBOLOGY + + if (fut.isValid()) + { + const QUuid csTaskId = QUuid::createUuid(); + DEBUG_LOG(self->m_logger) << LOGFUNCTION << " createSwatchAsync future valid: csTaskId=" << csTaskId; + + self->m_symbolPropertyImage2symbolResult[csTaskId] = symResult.toWeakRef(); + + QFutureWatcher *swatchWatcher = new QFutureWatcher(self); + swatchWatcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the QImage via the result() getter. + connect(swatchWatcher, &QFutureWatcher::finished, swatchWatcher, + [self, csTaskId, swatchWatcher] { + self->onEsriSymbolSwatchCompleted(csTaskId, swatchWatcher->result()); + swatchWatcher->deleteLater(); + }); + } + else + { + ERROR_LOG(self->m_logger) << LOGFUNCTION << " createSwatchAsync future invalid" + << " ThreadId=" << QThread::currentThreadId(); + } + }; + #ifdef CREATE_ESRI_SYMBOL_ON_DEMAND if (!eSym.esriSymbol) { - QObject *parent = const_cast(this); // cast const away - eSym.esriSymbol = eSym.m_sssResult.symbol(parent); // resultItem.symbol() has to be called from mainThread??? + // 300: symbol() (const, sync) is gone; fetchSymbolAsync() is non-const + async. Copy the + // (const) search result so the non-const fetchSymbolAsync() can be called on the copy. + Esri::ArcGISRuntime::SymbolStyleSearchResult sr(eSym.m_sssResult); + QFuture fut = sr.fetchSymbolAsync(self); + if (fut.isValid()) + { + QFutureWatcher *fetchWatcher = new QFutureWatcher(self); + fetchWatcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the Symbol* via the result() getter. + connect(fetchWatcher, &QFutureWatcher::finished, fetchWatcher, + [self, originalKey, startSwatchCreation, fetchWatcher] { + Esri::ArcGISRuntime::Symbol *symbol = fetchWatcher->result(); + if (symbol) + { + auto it = self->m_originalKeyToEsriSymbol.find(originalKey); + if (it != self->m_originalKeyToEsriSymbol.end()) + it.value().esriSymbol = symbol; + startSwatchCreation(); + } + fetchWatcher->deleteLater(); + }); + } + else + { + ERROR_LOG(self->m_logger) << LOGFUNCTION << " fetchSymbolAsync future invalid" + << " ThreadId=" << QThread::currentThreadId(); + } } -#endif // CREATE_ESRI_SYMBOL_ON_DEMAND - - connect(eSym.esriSymbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted, - this, &EsriDictionaryProcessor::onEsriSymbolSwatchCompleted, Qt::UniqueConnection); - -#ifdef USE_ARCADE_SYMBOLOGY - Esri::ArcGISRuntime::TaskWatcher tw; - auto it = m_baseSymbolCodeToSymbolPropertyTable.find(originalKey); - if (it != m_baseSymbolCodeToSymbolPropertyTable.end() && (it->geometryType() == GeometryType::Polygon) ) - // area/Polygon -> request rectangle, otherwise quadratic swatch: - tw = eSym.esriSymbol->createSwatch(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); else - tw = eSym.esriSymbol->createSwatch(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); - + { + startSwatchCreation(); + } #else - Esri::ArcGISRuntime::TaskWatcher tw = eSym.esriSymbol->createSwatch(CREATE_SWATCH_SCREEN_SCALE); -#endif // USE_ARCADE_SYMBOLOGY - QUuid csTaskId; - if (tw.isValid()) - { - csTaskId = tw.taskId(); - DEBUG_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher valid: csTaskId=" << csTaskId; - } - else - { - ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid" - << " ThreadId=" << QThread::currentThreadId(); - } - - if (! csTaskId.isNull()) - m_symbolPropertyImage2symbolResult[csTaskId] = symResult.toWeakRef(); - else - ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid"; + startSwatchCreation(); +#endif // CREATE_ESRI_SYMBOL_ON_DEMAND } else diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.h index 3daf4d0ae..7fbddfc24 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.h +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.h @@ -16,12 +16,14 @@ // Qt #include +#include #include // QUuid used in slot signatures / map keys (Qt6: no transitive include) // Esri #include #include // Esri::ArcGISRuntime::SymbolStyleSearchResult used below (no transitive include) +#include // complete type for the QFutureWatcher member (no transitive include) @@ -158,7 +160,7 @@ Q_SIGNALS: protected Q_SLOTS: - void onSearchSymbolsCompletedPropertyTable(QUuid /*ssTaskId*/, Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults); + void onSearchSymbolsCompletedPropertyTable(Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults); void onEsriSymbolSwatchCompleted(const QUuid &csTaskId, const QImage &image); @@ -177,6 +179,11 @@ protected: QList m_searchResultList; QList m_searchResultListValid; + // ArcGIS 300: async symbol search — replaces the removed 100.x TaskWatcher / + // searchSymbolsCompleted signal (searchSymbolsAsync() returns a QFuture<...>; the watcher + // delivers the result on this object's (main) thread). + QFutureWatcher m_searchSymbolsWatcher; + // originalKey struct EsriSymbol { diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.cpp index 3f2f83548..394533eef 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.cpp +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.cpp @@ -27,14 +27,15 @@ #include // ESRI -#include #include #include #include #include +#include // Esri::ArcGISRuntime::LoadStatus (300: enum class; Loadable.h only forward-declares it) +#include // Esri::ArcGISRuntime::Error (complete type; 300 dropped Error::code()) -# include // This file is created by RME, not ESRI +# include // This file is created by RME, not ESRI (300: flat include layout, no arcgis/ subdir) #if ACRGIS_VERSION >= 0x640100 // 100.1.0 / Update 1 # define SYM_PROTECTION_AREAS_BECOMES_LINE @@ -58,6 +59,7 @@ #include #include +#include #include #include @@ -393,29 +395,31 @@ bool SymbolDictionaryImpl::init(SymbologyType symbologyType, const QString &symb m_esri_dictionary = Esri::ArcGISRuntime::DictionarySymbolStyle::createFromFile(m_symbologyResourcePath); #else - // QString symbologyResourcePath("/usr/share/arcgis/resources/symbols//mil2525d/mil2525d.stylx"); + // ArcGIS 300: the 100.x DictionarySymbolStyle(specificationType[, path]) constructors are gone + // (300 loads a local .stylx via the static createFromFile(), as the ARCADE branch above). + // The 100.x "built-in" spec-only dictionary (empty path) has no 300 equivalent -> fail as the + // ARCADE branch does. if (symbologyResourcePath.isEmpty()) { - INFO_LOG(m_logger) << LOGFUNCTION << " create EsriDict:" - << " m_specificationType='" << m_specificationType << "'"; - m_esri_dictionary = new Esri::ArcGISRuntime::DictionarySymbolStyle(m_specificationType); + ERROR_LOG(m_logger) << LOGFUNCTION << " no symbologyResourcePath; 300 has no built-in spec-only DictionarySymbolStyle" + << " m_specificationType='" << m_specificationType << "'"; + Q_ASSERT(false); + return false; } - else - { - QFile resFile(symbologyResourcePath); - if (!resFile.exists()) - { - ERROR_LOG(m_logger) << LOGFUNCTION << " path does not exist: " << " symbologyResourcePath='" << symbologyResourcePath << "'"; - Q_ASSERT(false); - return false; - } - NOTICE_LOG(m_logger) << LOGFUNCTION << " create EsriDict:" - << " m_specificationType='" << m_specificationType << "'" - << " m_symbologyResourcePath='" << m_symbologyResourcePath << "'"; - m_esri_dictionary = new Esri::ArcGISRuntime::DictionarySymbolStyle(m_specificationType, m_symbologyResourcePath); + QFile resFile(symbologyResourcePath); + if (!resFile.exists()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " path does not exist: " << " symbologyResourcePath='" << symbologyResourcePath << "'"; + Q_ASSERT(false); + return false; } + NOTICE_LOG(m_logger) << LOGFUNCTION << " create EsriDict:" + << " m_specificationType='" << m_specificationType << "'" + << " m_symbologyResourcePath='" << m_symbologyResourcePath << "'"; + m_esri_dictionary = Esri::ArcGISRuntime::DictionarySymbolStyle::createFromFile(m_symbologyResourcePath); + #endif // USE_ARCADE_SYMBOLOGY if (m_esri_dictionary) @@ -485,11 +489,9 @@ bool SymbolDictionaryImpl::init(SymbologyType symbologyType, const QString &symb void SymbolDictionaryImpl::connectSignals() { - if (m_esri_dictionary) - { - connect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::fetchSymbolCompleted, - this, &SymbolDictionaryImpl::onFetchSymbolCompleted); - } + // ArcGIS 300: the 100.x fetchSymbolCompleted signal / TaskWatcher is gone. + // fetchSymbolAsync() now returns a QFuture and each fetchSymbol() request drives + // onFetchSymbolCompleted() via its own QFutureWatcher (see fetchSymbol() below). } @@ -1350,7 +1352,16 @@ QSharedPointer SymbolDictionaryImpl::fetchSymbol(const SymbolDataT #define FETCH_SYMBOL_ETS_CONCURRENT #ifdef FETCH_SYMBOL_ETS_CONCURRENT // symDataToRender contains flag 'symDataToRender' - symResult->setFuture(QtConcurrent::run(renderExtensions, extraImg, symDataToRender, colorToBeReplaced, symDataToRender.renderJustCoreImage)); + // Qt6: QtConcurrent::run(renderExtensions, constQImage&, ...) hits the QPromise overload + // (a const QImage& can't bind to renderExtensions' QImage& param) -> QFuture. Wrap in a + // lambda that returns QImage so run() yields QFuture. renderExtensions' first param + // is a non-const QImage& (it scales/replaces color in place), so pass a local non-const copy + // (QImage is implicitly shared: the capture + this local are shallow refcount bumps; the one + // deep copy is renderExtensions' own scaled() — same as 100.x, which mutated run()'s stored copy). + symResult->setFuture(QtConcurrent::run([extraImg, symDataToRender, colorToBeReplaced]() -> QImage { + QImage img = extraImg; + return renderExtensions(img, symDataToRender, colorToBeReplaced, symDataToRender.renderJustCoreImage); + })); #else QImage imageCopy = extraImg; @@ -1463,11 +1474,15 @@ QSharedPointer SymbolDictionaryImpl::fetchSymbol(const SymbolDataT const QVariantMap keys({{QStringLiteral("sidc"), SidcCodec::decodeRawSidc(sidc)}, // set sidc of symbol in case of MIL2525 {QStringLiteral("sic"), SidcCodec::decodeRawSidc(sidc)}}); // set sidc of symbol in case of APP6 - Esri::ArcGISRuntime::TaskWatcher tw = m_esri_dictionary->fetchSymbol(keys); + // ArcGIS 300: no TaskWatcher / fetchSymbolCompleted — fetchSymbolAsync() returns a + // QFuture. Correlate the future result back to this SymbolResult with our own + // QUuid (the SDK no longer issues a task id); a per-request QFutureWatcher delivers the + // Symbol* on this (main) thread -> onFetchSymbolCompleted(). + QFuture fut = m_esri_dictionary->fetchSymbolAsync(keys); QUuid fsTaskId; - if (tw.isValid()) + if (fut.isValid()) { - fsTaskId = tw.taskId(); + fsTaskId = QUuid::createUuid(); if (doParanoiaChecks()) if (fsTaskId.isNull()) @@ -1479,11 +1494,21 @@ QSharedPointer SymbolDictionaryImpl::fetchSymbol(const SymbolDataT // and put into map to be able to inform customer if swatch is completed: m_findSymbol2symbolResult[fsTaskId] = symResult.toWeakRef(); - DEBUG_LOG(m_logger) << LOGFUNCTION << " TaskWatcher valid: fsTaskId=" << fsTaskId; + DEBUG_LOG(m_logger) << LOGFUNCTION << " fetchSymbolAsync future valid: fsTaskId=" << fsTaskId; + + SymbolDictionaryImpl *const self = const_cast(this); // this method is const + QFutureWatcher *watcher = new QFutureWatcher(self); + watcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the Symbol* via the result() getter. + connect(watcher, &QFutureWatcher::finished, watcher, + [self, fsTaskId, watcher] { + self->onFetchSymbolCompleted(fsTaskId, watcher->result()); + watcher->deleteLater(); + }); } else { - ERROR_LOG(m_logger) << LOGFUNCTION << " TaskWatcher invalid" + ERROR_LOG(m_logger) << LOGFUNCTION << " fetchSymbolAsync future invalid" << " ThreadId=" << QThread::currentThreadId(); if (doParanoiaChecks()) @@ -1769,45 +1794,49 @@ void SymbolDictionaryImpl::onFetchSymbolCompleted(QUuid fsTaskId, Esri::ArcGISRu // retrieve Image : // { - connect(symbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted, - this, &SymbolDictionaryImpl::onSwatchCompleted); - + // ArcGIS 300: no TaskWatcher / createSwatchCompleted — createSwatchAsync() returns a + // QFuture. Correlate with our own csTaskId (SDK no longer issues a task id); a + // per-request QFutureWatcher delivers the QImage on the main thread -> onSwatchCompleted(). #ifdef USE_ARCADE_SYMBOLOGY - Esri::ArcGISRuntime::TaskWatcher tw; + QFuture fut; if (symResult->geometryType() == GeometryType::Polygon) // area/Polygon -> request rectangle, otherwise quadratic swatch: - tw = symbol->createSwatch(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); + fut = symbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); else - tw = symbol->createSwatch(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); + fut = symbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale); #else - Esri::ArcGISRuntime::TaskWatcher tw = symbol->createSwatch(CREATE_SWATCH_SCREEN_SCALE); // CREATE_SWATCH_SCREEN_SCALE + QFuture fut = symbol->createSwatchAsync(CREATE_SWATCH_SCREEN_SCALE); // CREATE_SWATCH_SCREEN_SCALE #endif // USE_ARCADE_SYMBOLOGY - QUuid csTaskId; - if (tw.isValid()) + + if (fut.isValid()) { - csTaskId = tw.taskId(); - DEBUG_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher valid: csTaskId=" << csTaskId; + QUuid csTaskId = QUuid::createUuid(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " createSwatchAsync future valid: csTaskId=" << csTaskId; if (doParanoiaChecks()) if (csTaskId.isNull()) abort(); + m_createSwatch2findSymbolTaskId[csTaskId] = fsTaskId; + + QFutureWatcher *watcher = new QFutureWatcher(this); + watcher->setFuture(fut); + // 300/Qt6: connect to finished() and read the QImage via the result() getter. + connect(watcher, &QFutureWatcher::finished, watcher, + [this, csTaskId, fsTaskId, symbol, watcher] { + onSwatchCompleted(csTaskId, watcher->result(), symbol); + watcher->deleteLater(); + }); } else { - ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid" + ERROR_LOG(m_logger) << LOGFUNCTION << " createSwatchAsync future invalid" << " ThreadId=" << QThread::currentThreadId(); if (doParanoiaChecks()) abort(); - } - if (! csTaskId.isNull()) - m_createSwatch2findSymbolTaskId[csTaskId] = fsTaskId; - else - ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid"; - } // retrieve Image TRACE_LOG(m_logger) << LOGFUNCTION << " END" @@ -1816,7 +1845,7 @@ void SymbolDictionaryImpl::onFetchSymbolCompleted(QUuid fsTaskId, Esri::ArcGISRu << " symbol= '" << symbol << "'"; } -void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage &image) +void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage &image, Esri::ArcGISRuntime::Symbol *symbol) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " symType= '" << this->symbologyType() << "'" @@ -1873,7 +1902,15 @@ void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage // start computation like: // QFuture QtConcurrent::run(Function function, arg1, arc2...) // ^^^^^^^^^^ without() - symResult->setFuture(QtConcurrent::run(renderExtensions, image, symToRender, QColor(), symToRender.renderJustCoreImage)); + // Qt6: same as above — the const image can't bind to renderExtensions' QImage& param via the + // raw-pointer form; wrap in a lambda returning QImage so run() yields QFuture. + // renderExtensions' first param is a non-const QImage& (it mutates in place); pass a local + // non-const copy (QImage is implicitly shared: capture + local are shallow; the one deep copy + // is renderExtensions' own scaled() — same as 100.x, which mutated run()'s stored copy). + symResult->setFuture(QtConcurrent::run([image, symToRender]() -> QImage { + QImage img = image; + return renderExtensions(img, symToRender, QColor(), symToRender.renderJustCoreImage); + })); #else // CONCURRENT @@ -1915,7 +1952,7 @@ void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage // see also: "SymbolDictionary - findSymbolCompleted without taskId" // https://earlyadopter.esri.com/project/forum/thread.html?cap=FA3A32558952489280FB472D26FA8192&forid={A612A2F3-E344-4BD0-A1EA-0CE2DA5E694E}&topid={432F3A6B-E323-4628-BF39-77DB7BAAC9A1} - Esri::ArcGISRuntime::Symbol *symbol = qobject_cast(sender()); + // ArcGIS 300: the symbol is passed in (the 100.x code recovered it via sender()). if (symbol) symbol->deleteLater(); @@ -2349,7 +2386,7 @@ QStringList SymbolDictionaryImpl::textFieldNamesInternal() const void SymbolDictionaryImpl::onErrorDictionary(const Esri::ArcGISRuntime::Error &error) { FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " - << " code=" << error.code() + << " code=" << error.arcGISServerErrorCode().value_or(-1) // 300: Error::code() removed << ", domain=" << static_cast(error.domain()) << ", extendedErrorType=" << static_cast(error.extendedErrorType()) << ", message='" << error.message() << "'" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.h index 2e270e0e6..34f61a0f0 100755 --- a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.h +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.h @@ -226,7 +226,7 @@ protected Q_SLOTS: void onFetchSymbolCompleted(QUuid fsTaskId, Esri::ArcGISRuntime::Symbol *symbol); - void onSwatchCompleted(const QUuid &csTaskId, const QImage &image); + void onSwatchCompleted(const QUuid &csTaskId, const QImage &image, Esri::ArcGISRuntime::Symbol *symbol); private: Esri::ArcGISRuntime::DictionarySymbolStyle *m_esri_dictionary = nullptr; diff --git a/qt6-after/qt6-migrated-legatus/MIGRATION_SUMMARY_ARCGIS300_SYMBOLOGY.md b/qt6-after/qt6-migrated-legatus/MIGRATION_SUMMARY_ARCGIS300_SYMBOLOGY.md new file mode 100644 index 000000000..d28a3ae45 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/MIGRATION_SUMMARY_ARCGIS300_SYMBOLOGY.md @@ -0,0 +1,200 @@ +# ArcGIS 300 Symbology Migration Summary + +Qt5→Qt6 + ArcGIS Runtime 100.x→ArcGIS Maps SDK for Qt **300.0.0** migration audit of the +SIDC/Esri symbology subsystem. Every change was compared against the legacy tree +(`qt5-before/qt5-legatus-legacy`); the goal is migration-only (no behavior change). +ArcGIS 300 SDK at `external/ArcGIS/MapsSDK_Qt_300.0.0/.../qt300.0.0/sdk` (flat include +layout — all headers directly in `sdk/include/`, no `arcgis/` subdirectory). + +## Scope audited + +- `Lts/Viewer/SituationMap/Symbology/` — SIDC codec/builder/dictionary/definitions (pure Qt). +- `Lts/Viewer/SituationMap/SymbologyImpl/` — `esri_dictionary_processor*.cpp`, + `base_symbol_model.*`, `symbol_dictionary_impl.*`, `advanced_symbology_logging.*`. +- `Lts/Viewer/SituationMap/SymbologyXML/` — symbol XML read/write/export. +- `Lts/Viewer/SituationMap/Map/` — the ArcGIS map layer (57 files). **Excluded from the + build** (see Remaining Issues). +- `Lts/Plugins/AdvancedSymbology/` — `asb_data_plugin.*`, `asb_foreign_interface_impl.*`, + `Gui/SymbolEditor/*`, `Gui/SymbolProxy/*`. +- `Lts/Services/CentralEffectSimulation/Plugins/Explosions/expand_polygon.cpp`. + +## Migration-only pattern classes found + +All in-scope changes classified as the following approved adaptations (no semantic change): + +- Qt include path churn: `#include ` → `#include ` (Gui/Widgets/Network/Sql/…). +- `qAsConst(x)` → `std::as_const(x)` (Qt6 removed `qAsConst`). +- `QRegExp` → `QRegularExpression`; `QRegExp::exactMatch(s)` → `QRegularExpression::match(s).hasMatch()`. +- `QString::count()` → `size()`; `leftRef/rightRef/midRef(n)` → `left/right/mid(n)`; + `split()` now returns `QStringList` (Qt6 dropped the `QStringView`/`QStringRef` ref + overloads) → `QVector` or `QStringList`; `.toString()` dropped where the + element is already a `QString` (Esri dictionary processors). +- `QAbstractItemModel::invalidateFilter()` → `beginFilterChange()`/`endFilterChange()` + (Qt6 removed `invalidateFilter()`). +- `QLinkedList`/`QLinkedListIterator` → STL `std::list` + range-for/iterators + (`append`→`push_back`, `remove`→`erase`, `toFront/toBack`→erase+`push_front`/`push_back`). +- `LOGFUNCTION` → `Q_FUNC_INFO` (no-op: `INGLLogger.h` defines `LOGFUNCTION` as `Q_FUNC_INFO`). +- `const QImage &x = …image()` → `const QImage x = …image()` (Qt6 `QImage::image()` by value). +- DDS-generated type rename `ParticipantManagement::Role_def::*` → `Role::*` (codegen artifact). +- ArcGIS include path: old 100.x `` → flat 300 `` (see below). +- Systematic 2-space→4-space re-indentation (whitespace-only; `diff -w` filters it out). + +## The four "verify carefully" items — resolutions + +1. **`Role_def` → `Role`** (`symbol_data_to_render_role.h`) — RESOLVED, 1:1. Legacy + `ppm_service.xml` and migrated `ppm_enums.idl` both define `Role` with the identical + 7 enumerators and values (`Unknown=0 … Suspect=6`). The `Role_def` name is a DDS-codegen + artifact (IDL name is `Role` in both; legacy codegen emitted `Role_def`, rtiddsgen 7.7 + emits `Role`). `role.underlying()` switch behaves identically; return values unchanged. + +2. **`#ifdef BUILD_WITH_EXTERNAL_DDS` role-conversion stub** — RESOLVED with a flagged delta. + (a) Real path = legacy 1:1 (constructor + `roleToRole<>(party.role())` identical). + (c) Stub documented (`// STUB: No external DDS …`), intentional migration scaffolding. + **(b) FLAGGED:** `BUILD_WITH_EXTERNAL_DDS` is referenced only as `#ifdef` in 28 source + files and is **never defined** (no `#define`, no CMake `set`/`option`/`-D`, empty + `CMAKE_CXX_FLAGS`). So the no-op **stub** path is what compiles in the current build — + a runtime behavior change vs legacy. This is codebase-wide (28 files) and pre-existing + in the migration (not introduced by the ArcGIS-300 symbology work); the stub is NOT + "fixed" (out of scope) — surfaced as a behavior delta. + +3. **Logger pattern** — RESOLVED, migration-only. `m_logger` is a **member** of type + `LOGGER_T` (`typedef NGL::INGLLogger &LOGGER_T;` — a *reference*) in **both** trees, + initialized in the constructor (`getLogger("SymbolDictionaryImpl"/"EsriDictionaryProcessor"/ + "BaseSymbolModel")` — names unchanged). A reference has no ownership → no + double-allocation/lifetime/init-order issue. (The "local→member" premise was inaccurate; + reality: unchanged.) + +4. **Filter API** (`base_symbol_model.cpp`) — RESOLVED, 1:1. 4 legacy `invalidateFilter()` + → 4 migrated `beginFilterChange(); endFilterChange();` pairs (`setValidBaseSymbolCodes`, + `onFilterPoints/Lines/Areas`); no leftover `invalidateFilter()`. `BaseSymbolProxyModel` + is a `QSortFilterProxyModel` (via `LtsColumnFilterProxyModel`) that overrides + `filterAcceptsRow` (unchanged); the changed members are plain `QSet` ops (no model + signals), so the back-to-back begin/end still triggers the proxy re-filter reading the + updated data. Behavior preserved. + +## Esri dictionary processors — dictionary equivalence + +The Qt6 API churn in `esri_dictionary_processor{,_app6,_app6d,_mil2525d}.cpp` +(`splitRef`→`split`, `rightRef`→`right`, `.toString()` removal) is migration-only. +The three `SymbologyImpl` files that use the ArcGIS symbol-search/swatch/fetch API — +`esri_dictionary_processor.cpp`, `symbol_dictionary_impl.cpp`, `base_symbol_model.cpp` — +were **ported from the 100.x `TaskWatcher` signal/slot model to the 300 `QFuture` async +model** (see "ArcGIS 300 QFuture port" below) and now **compile and link** against the 300 +SDK (see Build results). + +## ArcGIS 300 include-style reconciliation + +The 300 SDK is **flat** (`FIND_PATH(ARCGIS_INCLUDE_DIR ArcGISQt_global.h …)` in +`FindArcGIS.cmake` finds it directly in `sdk/include/`), so the correct form is **bare** +(`#include `), not ``. The migration already dropped the +prefix in the Esri processors; the Map layer + `expand_polygon` still carried it. The +**11** `arcgis/`-prefixed includes were reconciled to bare (one consistent form): + +- 10 ESRI headers (`SceneViewTypes`, `Error`, `CoreTypes`, `MapTypes`, `Viewpoint`, + `Camera`, `PolygonBuilder`, `GeometryEngine`) — exist flat in the SDK, resolve. +- 3 `arcgisversion.h` (custom RME header defining `ACRGIS_VERSION`, "created by RME, not + ESRI") — made bare for consistency; the header itself is **absent from the repo** + (pre-existing gap; see Remaining Issues). + +Files touched: `Map/situation_map_utils.{h,cpp}`, `Map/map_initializer.cpp`, +`Map/layer_factory.h`, `Explosions/expand_polygon.cpp`. + +## ArcGIS 300 QFuture port (SymbologyImpl) + +The 100.x ArcGIS Runtime exposed an async **task** API (`TaskWatcher` + per-task completion +signals); the 300 SDK replaced it with **futures** (`QFuture` + `QFutureWatcher`). +The three `SymbologyImpl` files were rewritten from the task model to the future model. +This is a **semantic** change — the approved "blocking work item" (chosen by the user) — and +every mapping below preserves the 100.x behavior (same work, same main-thread delivery, same +completion order). + +| 100.x (task API) | 300 (future API) | +|---|---| +| `searchSymbols(params) → TaskWatcher` + `searchSymbolsCompleted` | `searchSymbolsAsync(params) → QFuture` | +| `SymbolStyleSearchResult::symbol(QObject*) → Symbol*` (sync) | `fetchSymbolAsync(QObject*) → QFuture` | +| `Symbol::createSwatch(w,h,scale) → TaskWatcher` + `createSwatchCompleted` | `createSwatchAsync(...) → QFuture` | +| `DictionarySymbolStyle::fetchSymbol(map) → TaskWatcher` + `fetchSymbolCompleted` | `fetchSymbolAsync(map, QObject*) → QFuture` | +| `QtConcurrent::run(this, &Class::method)` | `QtConcurrent::run(&Class::method, this)` (Qt6: member fn first, receiver second) | + +Behavior-preserving details (each verified against the 300 headers): + +- **`QFutureWatcher` `result` signal/getter name collision.** `QFutureWatcher` declares both + a **signal** `void result(const T&)` and a **getter** `T result() const` — same name, so + `&QFutureWatcher::result` is ambiguous, and `QOverload(…)` / `qOverload(…)` both + fail (the getter poisons template deduction). Working form: connect to the unambiguous + `void finished()` signal and read the value via the `result()` getter + (`connect(w, &QFutureWatcher::finished, w, [w]{ auto v = w->result(); …; w->deleteLater(); });`). + `finished()` fires on the watcher's (main) thread once the future is done — the same main-thread + delivery the 100.x `TaskWatcher` completion signals gave. +- **Non-const `fetchSymbolAsync`.** `SymbolStyleSearchResult::fetchSymbolAsync(QObject*)` is + **non-const** (no `const` suffix, `SymbolStyleSearchResult.h`), so it can't be called on the + `const` search result stored in the map. Fix: copy the (cheap, shared_ptr-backed) + `SymbolStyleSearchResult` into a non-const local and call `fetchSymbolAsync` on the copy. +- **Swatch future correlation.** 100.x correlated a swatch to its request via the SDK-issued task + id; a 300 future carries no id. Fix: the per-request `QFutureWatcher` lambda captures a + caller-generated `QUuid` (`csTaskId`); `onEsriSymbolSwatchCompleted` is otherwise unchanged. + `Symbol::symbol()` (100.x const/sync) is gone → `fetchSymbolAsync` (async) under + `CREATE_ESRI_SYMBOL_ON_DEMAND`. +- **`QtConcurrent::run(renderExtensions, const QImage&, …)` → `QFuture`.** `renderExtensions` + takes a **non-const** `QImage&` (it scales / replaces color in place); passing a `const QImage&` + hit Qt6's `QPromise` overload → `QFuture` (unassignable to `QFuture`). Fix: wrap in a + lambda returning `QImage` that passes a **local non-const copy** (a `QImage` copy is a shallow + implicit-share refcount bump; the one deep copy is `renderExtensions`' own `scaled()`, exactly as + 100.x mutated `QtConcurrent::run`'s stored copy). +- **`DictionarySymbolStyle(QString)` / `(QString, QString)` ctors removed** → + `DictionarySymbolStyle::createFromFile(path)` (local `.stylx`); empty/missing path returns + `false` (mirrors the ARCADE branch; 300 has no built-in spec-only dict). +- **`Error::code()` removed** → `error.arcGISServerErrorCode().value_or(-1)`. +- **`LoadStatus` / `Error` incomplete** → `#include ` / `#include ` (300 + `LoadStatus` is an `enum class` in `MapTypes.h`; `Loadable.h` only forward-declares it). +- **`arcgis/arcgisversion.h` (custom RME header) dropped by 300** → a shim `arcgisversion.h` + was added to the 300 SDK include dir (`external/ArcGIS/…/sdk/include/`, so every `ArcGIS::ArcGIS` + consumer finds it) defining `ACRGIS_VERSION = 300000000` (> every 100.x `0x64xxxx` gate, so each + `#if ACRGIS_VERSION >= 0x64xxxx` takes the "newest SDK" branch; the single + `#if ACRGIS_VERSION < 0x640500` correctly takes its else). +- **Syntax fix:** a `// Qt6: leftRef removed` comment had swallowed the trailing `);` of two + `castToSymbolSet(… .left(2).toUInt())` statements (`base_symbol_model.cpp`); restructured so the + parens close before the comment. + +## Build results + +- **Pure-Qt symbology (green):** `situationMapCommon` and `advancedSymbologyXML` build + and link (in `build4` and reconfirmed under the ArcGIS-ON configure). These carry no + ArcGIS dependency. +- **ArcGIS-ON configure (green):** `BUILD_RTI=ON BUILD_GRANTLEE=ON BUILD_ARCGIS=ON` + configures successfully; `FindArcGIS.cmake` resolves the 300 SDK + (`Found ArcGIS: .../sdk/linux/x64/lib/libEsriRuntimeQt.a`). +- **ArcGIS-ON build of `advancedSymbology` (green):** after the QFuture port, the + ArcGIS-dependent target (`Symbology` SIDC codec + `SymbologyImpl` Esri processors, links + `ArcGIS::ArcGIS`) compiles and links — `libadvancedSymbology.so` built with **0 errors** + (build log `/tmp/arcgis300_targeted_build5.log`). +- **CTest (green for this change):** the 6 symbology-family `SymbologyXML.*` tests pass + (`ctest -R SymbologyXML` → 6/6). Full `migration_tests` suite: **604/644 pass, 0 actual + assertion failures** — the 40 non-passing are 31 "Not Run" (unbuilt test exes of unrelated + modules — ngl/dds/aes/bld_/taboc/wls/LtsDataView/Grantlee — a pre-existing build-scope gap, + not symbology) and 9 "Subprocess aborted" (display-dependent GUI tests; the 7 `LtsWidgetsQt6.*` + pass under `QT_QPA_PLATFORM=offscreen`, confirmed 10/10). None touch the symbology family. + +## Remaining issues / risks + +- **RESOLVED — `SymbologyImpl` 100.x ArcGIS task API (was the blocker).** The three + `SymbologyImpl` files previously used the 100.x `TaskWatcher` task API + (`searchSymbols`/`createSwatch`/`fetchSymbol` → `TaskWatcher` + completion signals), which the + 300 SDK removed. They were **ported to the 300 `QFuture` async model** (see "ArcGIS 300 QFuture + port" above) and now **compile and link** against the 300 SDK (`advancedSymbology` green). + This was a semantic change — the approved "blocking work item" chosen by the user — superseding + the earlier note that it was "out of scope for a verify-only task." + +- **Map layer excluded:** `SituationMap/CMakeLists.txt` has `ADD_SUBDIRECTORY(Map)`, + `MapImpl`, `MapObjects` **commented out**; no `Map/CMakeLists.txt`. The ArcGIS-300 Map + integration (beyond include paths — `Viewpoint`/`Camera`/`MapTypes` 300 API changes) is a + separate, not-yet-complete migration and is not in the current green build. +- **`BUILD_WITH_EXTERNAL_DDS` never defined** (see item 2): the DDS `roleToRole`/ + `ForeignInterfaceImpl` real path is dead code; the no-op stub compiles. Behavior delta + vs legacy, codebase-wide, pre-existing in the migration. +- **`arcgisversion.h` (custom RME header) — RESOLVED:** the 300 SDK dropped this header (it + defined `ACRGIS_VERSION` for the `#if ACRGIS_VERSION >= …` version gates). A shim + `arcgisversion.h` was added to the 300 SDK include dir (`external/ArcGIS/…/sdk/include/`, + `ACRGIS_VERSION = 300000000`) so every gate takes the "newest SDK" branch (see the QFuture-port + section). It lives in the external SDK tree (not the Legatus repo) because it is a drop-in + replacement for the SDK-shipped header.