From 199d5f9b9b620c8d9feff9ee6da6c4be6d543ed2 Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sat, 9 Aug 2025 20:59:44 +0800 Subject: [PATCH 01/10] Convert surveys list widget to tree widget --- src/gui/ViewDialog.cpp | 38 +++++++++++++++++++------------------- src/gui/ViewDialog.hpp | 3 ++- src/gui/viewDialog.ui | 25 ++++++++++++++++++------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index 2742fc0592ac5..48782e5a860a5 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -167,7 +167,7 @@ void ViewDialog::createDialogContent() connect(ui->stackListWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(changePage(QListWidgetItem *, QListWidgetItem*))); // Kinetic scrolling kineticScrollingList << ui->projectionListWidget << ui->culturesListWidget << ui->skyCultureTextBrowser << ui->landscapesListWidget - << ui->landscapeTextBrowser << ui->surveysListWidget << ui->surveysTextBrowser; + << ui->landscapeTextBrowser << ui->surveysTreeWidget << ui->surveysTextBrowser; StelGui* gui= dynamic_cast(StelApp::getInstance().getGui()); if (gui) { @@ -657,8 +657,8 @@ void ViewDialog::createDialogContent() connect(ui->surveyTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateHips())); connect(ui->stackListWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(updateHips())); - connect(ui->surveysListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(updateHips()), Qt::QueuedConnection); - connect(ui->surveysListWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(hipsListItemChanged(QListWidgetItem*))); + connect(ui->surveysTreeWidget, &QTreeWidget::currentItemChanged, this, &ViewDialog::updateHips, Qt::QueuedConnection); + connect(ui->surveysTreeWidget, &QTreeWidget::itemChanged, this, &ViewDialog::hipsListItemChanged); connect(ui->surveysFilter, &QLineEdit::textChanged, this, &ViewDialog::filterSurveys); updateHips(); @@ -748,17 +748,17 @@ void ViewDialog::updateHips() selectedType = "dss"; // Update survey list. - QListWidget* l = ui->surveysListWidget; + auto*const l = ui->surveysTreeWidget; if (!hipsmgr->property("loaded").toBool()) { l->clear(); - new QListWidgetItem(q_("Loading..."), l); + new QTreeWidgetItem(l, {q_("Loading...")}); return; } - QString currentSurvey = l->currentItem() ? l->currentItem()->data(Qt::UserRole).toString() : ""; - QListWidgetItem* currentItem = nullptr; + QString currentSurvey = l->currentItem() ? l->currentItem()->data(0, Qt::UserRole).toString() : ""; + QTreeWidgetItem* currentItem = nullptr; HipsSurveyP currentHips; l->blockSignals(true); @@ -774,10 +774,10 @@ void ViewDialog::updateHips() QString title = properties["obs_title"].toString(); if (title.isEmpty()) continue; - QListWidgetItem* item = new QListWidgetItem(title, l); + QTreeWidgetItem* item = new QTreeWidgetItem(l, {title}); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); - item->setData(Qt::UserRole, url); + item->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); + item->setData(0, Qt::UserRole, url); if (url == currentSurvey) { currentItem = item; @@ -786,7 +786,7 @@ void ViewDialog::updateHips() disconnect(hips.data(), nullptr, this, nullptr); connect(hips.data(), SIGNAL(statusChanged()), this, SLOT(updateHips())); } - l->sortItems(Qt::AscendingOrder); + l->sortItems(0, Qt::AscendingOrder); l->setCurrentItem(currentItem); l->scrollToItem(currentItem); l->blockSignals(false); @@ -859,27 +859,27 @@ void ViewDialog::toggleHipsDialog() void ViewDialog::filterSurveys() { const QString pattern = ui->surveysFilter->text().simplified(); - const auto& list = *ui->surveysListWidget; - for (int row = 0; row < list.count(); ++row) + const auto& list = *ui->surveysTreeWidget; + for (int row = 0; row < list.topLevelItemCount(); ++row) { - auto& item = *list.item(row); - const QString text = item.text().simplified(); + auto& item = *list.topLevelItem(row); + const QString text = item.text(0).simplified(); const bool show = pattern.isEmpty() || text.contains(pattern, Qt::CaseInsensitive); item.setHidden(!show); } } -void ViewDialog::hipsListItemChanged(QListWidgetItem* item) +void ViewDialog::hipsListItemChanged(QTreeWidgetItem* item) { - QListWidget* l = item->listWidget(); + auto*const l = item->treeWidget(); l->blockSignals(true); StelModule *hipsmgr = StelApp::getInstance().getModule("HipsMgr"); - QString url = item->data(Qt::UserRole).toString(); + QString url = item->data(0, Qt::UserRole).toString(); HipsSurveyP hips; QMetaObject::invokeMethod(hipsmgr, "getSurveyByUrl", Qt::DirectConnection, Q_RETURN_ARG(HipsSurveyP, hips), Q_ARG(QString, url)); Q_ASSERT(hips); - if (item->checkState() == Qt::Checked) + if (item->checkState(0) == Qt::Checked) { l->setCurrentItem(item); hips->setProperty("visible", true); diff --git a/src/gui/ViewDialog.hpp b/src/gui/ViewDialog.hpp index 14ae74da200df..21dc1c00a15de 100644 --- a/src/gui/ViewDialog.hpp +++ b/src/gui/ViewDialog.hpp @@ -27,6 +27,7 @@ class Ui_viewDialogForm; class QListWidgetItem; +class QTreeWidgetItem; class QToolButton; class AddRemoveLandscapesDialog; @@ -97,7 +98,7 @@ private slots: void updateHips(); void filterSurveys(); - void hipsListItemChanged(QListWidgetItem* item); + void hipsListItemChanged(QTreeWidgetItem* item); void populateHipsGroups(); void toggleHipsDialog(); diff --git a/src/gui/viewDialog.ui b/src/gui/viewDialog.ui index 855e933a8718d..94daa1a919de6 100644 --- a/src/gui/viewDialog.ui +++ b/src/gui/viewDialog.ui @@ -5904,13 +5904,6 @@ - - - - Qt::ScrollBarAlwaysOff - - - @@ -5921,6 +5914,24 @@ + + + + Qt::ScrollBarAlwaysOff + + + false + + + true + + + + + + + + From 011b4f784b40d07110246e60e9660dccb365adac Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sat, 9 Aug 2025 18:34:08 +0800 Subject: [PATCH 02/10] Make it possible to have multiple HiPS per planet --- src/core/StelHips.cpp | 16 ++- src/core/StelHips.hpp | 10 +- src/core/modules/HipsMgr.cpp | 2 +- src/core/modules/Planet.cpp | 36 ++++-- src/core/modules/Planet.hpp | 16 ++- src/core/modules/SolarSystem.cpp | 47 ++------ src/core/modules/SolarSystem.hpp | 6 +- src/gui/ViewDialog.cpp | 192 +++++++++++++++++++++++++++---- src/gui/ViewDialog.hpp | 7 ++ 9 files changed, 249 insertions(+), 83 deletions(-) diff --git a/src/core/StelHips.cpp b/src/core/StelHips.cpp index 7b4899d00d6ae..bbbdad822a044 100644 --- a/src/core/StelHips.cpp +++ b/src/core/StelHips.cpp @@ -66,9 +66,10 @@ QUrl HipsSurvey::getUrlFor(const QString& path) const return QString("%1/%2%3").arg(base.url(), path, args); } -HipsSurvey::HipsSurvey(const QString& url_, const QString& frame, const QString& type, +HipsSurvey::HipsSurvey(const QString& url_, const QString& group, const QString& frame, const QString& type, const QMap& hipslistProps, const double releaseDate_) : url(url_), + group(group), type(type), hipsFrame(frame), releaseDate(releaseDate_), @@ -219,6 +220,9 @@ void HipsSurvey::setNormalsSurvey(const HipsSurveyP& normals) return; } this->normals = normals; + // Resetting normals should result in clearing normal maps from all + // the tiles. The easiest way to do this is to remove the tiles. + if (!normals) tiles.clear(); } void HipsSurvey::setHorizonsSurvey(const HipsSurveyP& horizons) @@ -229,6 +233,9 @@ void HipsSurvey::setHorizonsSurvey(const HipsSurveyP& horizons) return; } this->horizons = horizons; + // Resetting horizons should result in clearing horizon maps from all + // the tiles. The easiest way to do this is to remove the tiles. + if (!horizons) tiles.clear(); } void HipsSurvey::draw(StelPainter* sPainter, double angle, HipsSurvey::DrawCallback callback) @@ -675,7 +682,7 @@ int HipsSurvey::fillArrays(int order, int pix, int drawOrder, int splitOrder, } //! Parse a hipslist file into a list of surveys. -QList HipsSurvey::parseHipslist(const QString& data) +QList HipsSurvey::parseHipslist(const QString& hipslistURL, const QString& data) { QList ret; static const QString defaultFrame = "equatorial"; @@ -685,6 +692,7 @@ QList HipsSurvey::parseHipslist(const QString& data) QString type; QString frame = defaultFrame; QString status; + QString group = hipslistURL; double releaseDate = 0; QMap hipslistProps; for (const auto &line : entry.split('\n')) @@ -715,9 +723,11 @@ QList HipsSurvey::parseHipslist(const QString& data) type = value.toLower(); else if (key == "hips_status") status = value.toLower(); + else if (key == "group") + group = value; } if(status.split(' ').contains("public")) - ret.append(HipsSurveyP(new HipsSurvey(url, frame, type, hipslistProps, releaseDate))); + ret.append(HipsSurveyP(new HipsSurvey(url, group, frame, type, hipslistProps, releaseDate))); } return ret; } diff --git a/src/core/StelHips.hpp b/src/core/StelHips.hpp index 3b3407f227f54..6c3be047b4362 100644 --- a/src/core/StelHips.hpp +++ b/src/core/StelHips.hpp @@ -60,10 +60,12 @@ class HipsSurvey : public QObject const QVector& indices)> DrawCallback; //! Create a new HipsSurvey from its url. //! @param url The location of the survey. + //! @param group A string that identifies a group of color/normals/horizons + //! surveys that this survey belongs to. //! @param frame The reference frame from the survey's \c hips_frame property. //! @param type Survey type from the survey's \c type property. //! @param releaseDate If known the UTC JD release date of the survey. Used for cache busting. - HipsSurvey(const QString& url, const QString& frame, const QString& type, + HipsSurvey(const QString& url, const QString& group, const QString& frame, const QString& type, const QMap& hipslistProps, double releaseDate=0.0); ~HipsSurvey() override; @@ -91,6 +93,9 @@ class HipsSurvey : public QObject //! Return the type of the survey (its \c type property). QString getType() const { return type; } + //! Return the group of of color/normals/horizons surveys that this survey belongs to. + QString getGroup() const { return group; } + //! Get whether the survey is still loading. bool isLoading(void) const; @@ -100,7 +105,7 @@ class HipsSurvey : public QObject void setHorizonsSurvey(const HipsSurveyP& horizons); //! Parse a hipslist file into a list of surveys. - static QList parseHipslist(const QString& data); + static QList parseHipslist(const QString& hipslistURL, const QString& data); signals: void propertiesChanged(void); @@ -113,6 +118,7 @@ class HipsSurvey : public QObject private: LinearFader fader; QString url; + QString group; QString type; QString hipsFrame; QString planet; diff --git a/src/core/modules/HipsMgr.cpp b/src/core/modules/HipsMgr.cpp index e20c26c60cea4..2cacca9c859e5 100644 --- a/src/core/modules/HipsMgr.cpp +++ b/src/core/modules/HipsMgr.cpp @@ -114,7 +114,7 @@ void HipsMgr::loadSources() QNetworkReply* networkReply = StelApp::getInstance().getNetworkAccessManager()->get(req); connect(networkReply, &QNetworkReply::finished, this, [=] { QByteArray data = networkReply->readAll(); - QList newSurveys = HipsSurvey::parseHipslist(data); + QList newSurveys = HipsSurvey::parseHipslist(source.toString(), data); for (HipsSurveyP &survey: newSurveys) { connect(survey.data(), SIGNAL(propertiesChanged()), this, SIGNAL(surveysChanged())); diff --git a/src/core/modules/Planet.cpp b/src/core/modules/Planet.cpp index 92ddd3451a7b8..c1d6b1d64213b 100644 --- a/src/core/modules/Planet.cpp +++ b/src/core/modules/Planet.cpp @@ -246,7 +246,6 @@ Planet::Planet(const QString& englishName, axisRotation(0.f), objModel(Q_NULLPTR), objModelLoader(Q_NULLPTR), - survey(Q_NULLPTR), rings(Q_NULLPTR), distance(0.0), sphereScale(1.), @@ -397,6 +396,27 @@ void Planet::replaceTexture(const QString &texName) } } +void Planet::setSurvey(const HipsSurveyP& colors, const HipsSurveyP& normals, const HipsSurveyP& horizons) +{ + Q_ASSERT(colors); + Q_ASSERT(colors->getType() == "planet"); + Q_ASSERT(!normals || normals->getType() == "planet-normal"); + Q_ASSERT(!horizons || horizons->getType() == "planet-horizon"); + Q_ASSERT(!normals || normals->getFrame() == colors->getFrame()); + Q_ASSERT(!horizons || horizons->getFrame() == colors->getFrame()); + + survey.colors = colors; + survey.normals = normals; + survey.horizons = horizons; + + if (survey.normals) + survey.colors->setNormalsSurvey(survey.normals); + if (survey.horizons) + survey.colors->setHorizonsSurvey(survey.horizons); + + survey.colors->setProperty("planet", getEnglishName()); +} + void Planet::translateName(const StelTranslator& trans) { nameI18 = trans.tryQtranslate(englishName, getContextString()); @@ -3748,12 +3768,12 @@ void Planet::draw3dModel(StelCore* core, StelProjector::ModelViewTranformP trans drawSphere(&sPainter, screenRd, drawOnlyRing); } } - else if (!survey || survey->getInterstate() < 1.0f) + else if (!survey || survey.colors->getInterstate() < 1.0f) { drawSphere(&sPainter, screenRd, drawOnlyRing); } - if (survey && survey->getInterstate() > 0.0f) + if (survey && survey.colors->getInterstate() > 0.0f) { drawSurvey(core, &sPainter); drawSphere(&sPainter, screenRd, true); @@ -4374,11 +4394,6 @@ void Planet::drawSurvey(StelCore* core, StelPainter* painter) if (!Planet::initShader()) return; static SolarSystem* ssm = GETSTELMODULE(SolarSystem); - if (surveyForNormals && survey) - survey->setNormalsSurvey(surveyForNormals); - if (surveyForHorizons && survey) - survey->setHorizonsSurvey(surveyForHorizons); - painter->setDepthMask(true); painter->setDepthTest(true); @@ -4402,7 +4417,7 @@ void Planet::drawSurvey(StelCore* core, StelPainter* painter) } GL(shader->bind()); - RenderData rData = setCommonShaderUniforms(*painter, shader, *shaderVars, surveyForNormals != nullptr, surveyForHorizons != nullptr); + RenderData rData = setCommonShaderUniforms(*painter, shader, *shaderVars, !!survey.normals, !!survey.horizons); QVector projectedVertsArray; QVector vertsArray; const double angle = 2 * getSpheroidAngularRadius(core) * M_PI_180; @@ -4432,7 +4447,8 @@ void Planet::drawSurvey(StelCore* core, StelPainter* painter) painter->getProjector()->getModelViewTransform()->combine(Mat4d::zrotation(M_PI * 0.5)); painter->getProjector()->getModelViewTransform()->combine(Mat4d::scaling(Vec3d(1, 1, oneMinusOblateness))); - survey->draw(painter, angle, [&](const QVector& verts, const QVector& tex, const QVector& indices) { + survey.colors->draw(painter, angle, [&](const QVector& verts, const QVector& tex, + const QVector& indices) { projectedVertsArray.resize(verts.size()); vertsArray.resize(verts.size()); for (int i = 0; i < verts.size(); i++) diff --git a/src/core/modules/Planet.hpp b/src/core/modules/Planet.hpp index 863aa0c3a5686..594309f092c2e 100644 --- a/src/core/modules/Planet.hpp +++ b/src/core/modules/Planet.hpp @@ -670,7 +670,9 @@ class Planet : public StelObject void resetTextures(); //! Use texture from @param texName (must reside inside the scripts directory!) void replaceTexture(const QString& texName); - + + void setSurvey(const HipsSurveyP& colors, const HipsSurveyP& normals, const HipsSurveyP& horizons); + protected: // These components for getInfoString() can be overridden in subclasses virtual QString getInfoStringName(const StelCore *core, const InfoStringGroup& flags) const; @@ -808,9 +810,15 @@ class Planet : public StelObject QFuture* objModelLoader; //!< For async loading of the OBJ file QString objModelPath; - HipsSurveyP survey; - HipsSurveyP surveyForNormals; - HipsSurveyP surveyForHorizons; + struct SurveyPack + { + HipsSurveyP colors; + HipsSurveyP normals; + HipsSurveyP horizons; + operator bool() const { return !!colors; } + }; + + SurveyPack survey; Ring* rings; //!< Planet rings double distance; //!< Temporary variable used to store the distance to a given point diff --git a/src/core/modules/SolarSystem.cpp b/src/core/modules/SolarSystem.cpp index 0a8825e68f10b..7e227495bd8a5 100644 --- a/src/core/modules/SolarSystem.cpp +++ b/src/core/modules/SolarSystem.cpp @@ -357,9 +357,6 @@ void SolarSystem::init() addAction("actionShow_Planets_EnlargeSun", displayGroup, N_("Enlarge Sun"), "flagSunScale"); addAction("actionShow_Planets_ShowMinorBodyMarkers", displayGroup, N_("Mark minor bodies"), "flagMarkers"); - connect(StelApp::getInstance().getModule("HipsMgr"), SIGNAL(gotNewSurvey(HipsSurveyP)), - this, SLOT(onNewSurvey(HipsSurveyP))); - // Fill ephemeris dates connect(this, SIGNAL(requestEphemerisVisualization()), this, SLOT(fillEphemerisDates())); connect(this, SIGNAL(ephemerisDataStepChanged(int)), this, SLOT(fillEphemerisDates())); @@ -4174,40 +4171,6 @@ bool SolarSystem::removeMinorPlanet(const QString &name) return true; } -void SolarSystem::onNewSurvey(HipsSurveyP survey) -{ - if (!survey->isPlanetarySurvey()) return; - - const auto type = survey->getType(); - const bool isPlanetColor = type == "planet"; - const bool isPlanetNormal = type == "planet-normal"; - const bool isPlanetHorizon = type == "planet-horizon"; - if (!isPlanetColor && !isPlanetNormal && !isPlanetHorizon) - return; - - QString planetName = survey->getFrame(); - PlanetP pl = searchByEnglishName(planetName); - if (!pl) return; - if (isPlanetColor) - { - if (pl->survey) return; - pl->survey = survey; - } - else if (isPlanetNormal) - { - if (pl->surveyForNormals) return; - pl->surveyForNormals = survey; - } - else if (isPlanetHorizon) - { - if (pl->surveyForHorizons) return; - pl->surveyForHorizons = survey; - } - survey->setProperty("planet", pl->getEnglishName()); - // Not visible by default for the moment. - survey->setProperty("visible", false); -} - void SolarSystem::setExtraThreads(int n) { extraThreads=qBound(0,n,QThreadPool::globalInstance()->maxThreadCount()-1); @@ -4232,3 +4195,13 @@ const QMap SolarSystem::vMagAlgorit {Planet::Generic, "Generic"}, {Planet::UndefinedAlgorithm, ""} }; + +void SolarSystem::enableSurvey(const HipsSurveyP& colors, const HipsSurveyP& normals, const HipsSurveyP& horizons) +{ + Q_ASSERT(colors); + QString planetName = colors->getFrame(); + PlanetP pl = searchByEnglishName(planetName); + if (!pl) return; + + pl->setSurvey(colors, normals, horizons); +} diff --git a/src/core/modules/SolarSystem.hpp b/src/core/modules/SolarSystem.hpp index ac444daae7588..9583eab11173e 100644 --- a/src/core/modules/SolarSystem.hpp +++ b/src/core/modules/SolarSystem.hpp @@ -787,6 +787,9 @@ public slots: //! Configure the limiting absolute magnitude for plotting minor bodies. Configured value is clamped to -2..37 (practical limit) void setMarkerMagThreshold(double m); + //! Enable the survey for use on the planet it describes + void enableSurvey(const HipsSurveyP& colors, const HipsSurveyP& normals, const HipsSurveyP& horizons); + signals: void labelsDisplayedChanged(bool b); void flagOrbitsChanged(bool b); @@ -1057,9 +1060,6 @@ private slots: void setEphemerisSaturnMarkerColor(const Vec3f& c); Vec3f getEphemerisSaturnMarkerColor(void) const; - //! Called when a new Hips survey has been loaded by the hips mgr. - void onNewSurvey(HipsSurveyP survey); - //! Taking the JD dates for each ephemeride and preparation the human readable dates according to the settings for dates void fillEphemerisDates(); diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index 48782e5a860a5..a1ce161952fc1 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -21,6 +21,7 @@ #include "ViewDialog.hpp" #include "ui_viewDialog.h" +#include #include "AddRemoveLandscapesDialog.hpp" #include "AtmosphereDialog.hpp" #include "SkylightDialog.hpp" @@ -31,6 +32,7 @@ #include "StelApp.hpp" #include "StelCore.hpp" #include "StelModule.hpp" +#include "StelMainView.hpp" #include "LandscapeMgr.hpp" #include "StelSkyCultureMgr.hpp" #include "ConstellationMgr.hpp" @@ -76,6 +78,26 @@ struct Page }; }; +struct HipsRole +{ + enum + { + URL = Qt::UserRole, + PlanetEnglishName, + ItemType, + }; +}; + +struct HipsItemType +{ + enum + { + Survey, // The HiPS itself + Planet, // The item representing a planet and containing groups of surveys + Group, // Group of planetary surveys: albedo+normals+horizons + }; +}; + ViewDialog::ViewDialog(QObject* parent) : StelDialog("View", parent) , addRemoveLandscapesDialog(nullptr) , atmosphereDialog(nullptr) @@ -727,10 +749,10 @@ void ViewDialog::populateNomenclatureControls(bool flag) // Heuristic function to decide in which group to put a survey. static QString getHipsType(const HipsSurveyP hips) { - QJsonObject properties = hips->property("properties").toJsonObject(); if (!hips->isPlanetarySurvey()) return "dss"; - if (properties["type"].toString() == "planet") // TODO: switch to use hips->isPlanetarySurvey() and multiple surveys for Solar system bodies + const auto type = hips->getType(); + if (type == "planet" || type == "planet-normal" || type == "planet-horizon") return "sol"; return "other"; } @@ -756,8 +778,9 @@ void ViewDialog::updateHips() new QTreeWidgetItem(l, {q_("Loading...")}); return; } + planetarySurveys.clear(); - QString currentSurvey = l->currentItem() ? l->currentItem()->data(0, Qt::UserRole).toString() : ""; + QString currentSurvey = l->currentItem() ? l->currentItem()->data(0, HipsRole::URL).toString() : ""; QTreeWidgetItem* currentItem = nullptr; HipsSurveyP currentHips; @@ -765,6 +788,9 @@ void ViewDialog::updateHips() l->clear(); const QList hipslist = hipsmgr->property("surveys").value>(); + const bool selectedIsPlanetary = selectedType == "sol"; + l->setRootIsDecorated(selectedIsPlanetary); + const auto& solarSys = GETSTELMODULE(SolarSystem); for (auto &hips: hipslist) { if (getHipsType(hips) != selectedType) @@ -774,17 +800,68 @@ void ViewDialog::updateHips() QString title = properties["obs_title"].toString(); if (title.isEmpty()) continue; - QTreeWidgetItem* item = new QTreeWidgetItem(l, {title}); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); - item->setData(0, Qt::UserRole, url); - if (url == currentSurvey) + if (selectedIsPlanetary) + { + const auto& englishName = hips->getFrame(); + const auto& group = hips->getGroup(); + auto& planetData = planetarySurveys[englishName]; + if(!planetData.planetItem) + { + const auto& planet = solarSys->searchByEnglishName(englishName); + if (!planet) + { + qWarning().nospace() << "Found a planetary survey with unknown frame: " + << englishName << ". Skipping it."; + continue; + } + planetData.planetItem = new QTreeWidgetItem(l, {planet->getNameI18n()}); + planetData.planetItem->setData(0, HipsRole::ItemType, HipsItemType::Planet); + planetData.planetItem->setData(0, HipsRole::PlanetEnglishName, englishName); + } + QTreeWidgetItem*& groupItem = planetData.groupsMap[group]; + if (!groupItem) + { + groupItem = new QTreeWidgetItem(planetData.planetItem, {group}); + groupItem->setData(0, HipsRole::ItemType, HipsItemType::Group); + groupItem->setFlags(groupItem->flags() | Qt::ItemIsUserCheckable); + groupItem->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); + } + static const QHash typeNames = { + {L1S("planet"), q_("Albedo")}, + {L1S("planet-normal"), q_("Normal map")}, + {L1S("planet-horizon"), q_("Horizon map")}, + }; + const auto type = hips->getType(); + QString typeName = type; + if (const auto it = typeNames.find(type); it != typeNames.end()) + typeName = it.value(); + const auto surveyItem = new QTreeWidgetItem(groupItem, {title, typeName}); + surveyItem->setData(0, HipsRole::ItemType, HipsItemType::Survey); + surveyItem->setFlags(surveyItem->flags() | Qt::ItemIsUserCheckable); + surveyItem->setData(0, HipsRole::URL, url); + if (url == currentSurvey) + { + currentItem = surveyItem; + currentHips = hips; + groupItem->setExpanded(true); + planetData.planetItem->setExpanded(true); + } + } + else { - currentItem = item; - currentHips = hips; + QTreeWidgetItem* item = new QTreeWidgetItem(l, {title}); + item->setData(0, HipsRole::ItemType, HipsItemType::Survey); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); + item->setData(0, HipsRole::URL, url); + if (url == currentSurvey) + { + currentItem = item; + currentHips = hips; + } + disconnect(hips.data(), nullptr, this, nullptr); + connect(hips.data(), SIGNAL(statusChanged()), this, SLOT(updateHips())); } - disconnect(hips.data(), nullptr, this, nullptr); - connect(hips.data(), SIGNAL(statusChanged()), this, SLOT(updateHips())); } l->sortItems(0, Qt::AscendingOrder); l->setCurrentItem(currentItem); @@ -873,21 +950,90 @@ void ViewDialog::hipsListItemChanged(QTreeWidgetItem* item) { auto*const l = item->treeWidget(); l->blockSignals(true); - StelModule *hipsmgr = StelApp::getInstance().getModule("HipsMgr"); - QString url = item->data(0, Qt::UserRole).toString(); - HipsSurveyP hips; - QMetaObject::invokeMethod(hipsmgr, "getSurveyByUrl", Qt::DirectConnection, - Q_RETURN_ARG(HipsSurveyP, hips), Q_ARG(QString, url)); - Q_ASSERT(hips); - if (item->checkState(0) == Qt::Checked) + const auto hipsmgr = qobject_cast(StelApp::getInstance().getModule("HipsMgr")); + + switch (item->data(0, HipsRole::ItemType).toInt()) { - l->setCurrentItem(item); - hips->setProperty("visible", true); + case HipsItemType::Survey: + { + QString url = item->data(0, HipsRole::URL).toString(); + const auto hips = hipsmgr->getSurveyByUrl(url); + Q_ASSERT(hips); + if (item->checkState(0) == Qt::Checked) + { + l->setCurrentItem(item); + hips->setProperty("visible", true); + } + else + { + hips->setProperty("visible", false); + } + break; } - else + case HipsItemType::Group: { - hips->setProperty("visible", false); + // First, uncheck all the sibling groups except the one we're enabling + if (item->checkState(0) == Qt::Checked) + { + const auto planetItem = item->parent(); + Q_ASSERT(planetItem); + Q_ASSERT(planetItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Planet); + for (int n = 0; n < planetItem->childCount(); ++n) + { + const auto groupItem = planetItem->child(n); + Q_ASSERT(groupItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Group); + if (groupItem == item) continue; + if (groupItem->checkState(0) == Qt::Checked) + { + groupItem->setCheckState(0, Qt::Unchecked); + hipsListItemChanged(groupItem); + } + } + } + + // Now configure the survey chosen + HipsSurveyP colors; + HipsSurveyP normals; + HipsSurveyP horizons; + for (int n = 0; n < item->childCount(); ++n) + { + const auto child = item->child(n); + const auto url = child->data(0, HipsRole::URL).toString(); + const auto hips = hipsmgr->getSurveyByUrl(url); + Q_ASSERT(hips); + const auto type = hips->getType(); + if (type == L1S("planet")) + colors = hips; + else if (type == L1S("planet-normal")) + normals = hips; + else if (type == L1S("planet-horizon")) + horizons = hips; + } + if (item->checkState(0) == Qt::Checked) + { + if (!colors) + { + QMessageBox::critical(&StelMainView::getInstance(), q_("No albedo map"), + q_("This group of surveys doesn't have an albedo " + "map. Can't display it.")); + return; + } + colors->setProperty("visible", true); + const auto solarSys = qobject_cast(StelApp::getInstance().getModule("SolarSystem")); + solarSys->enableSurvey(colors, normals, horizons); + } + else + { + if (colors) colors->setProperty("visible", false); + } + break; } + case HipsItemType::Planet: + { + break; + } + } + l->blockSignals(false); } diff --git a/src/gui/ViewDialog.hpp b/src/gui/ViewDialog.hpp index 21dc1c00a15de..aec738b3f8e5f 100644 --- a/src/gui/ViewDialog.hpp +++ b/src/gui/ViewDialog.hpp @@ -24,6 +24,7 @@ #include #include +#include class Ui_viewDialogForm; class QListWidgetItem; @@ -123,6 +124,12 @@ private slots: ConfigureDSOColorsDialog * configureDSOColorsDialog; ConfigureOrbitColorsDialog * configureOrbitColorsDialog; QTimer hipsUpdateTimer; + struct PlanetSurveyPack + { + QTreeWidgetItem* planetItem = nullptr; + QHash groupsMap; + }; + QHash planetarySurveys; }; #endif // _VIEWDIALOG_HPP From 1ea52bb4b65a0e074a2fa887bdb4b855d30205f3 Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sun, 17 Aug 2025 13:20:41 +0800 Subject: [PATCH 03/10] Don't recreate HiPS list on every small update Not only is this very inefficient, but it also resets the expandedness state of tree items, which needs careful management. Now we simply don't touch the items already present. --- src/gui/ViewDialog.cpp | 141 +++++++++++++++++++++++------------------ src/gui/ViewDialog.hpp | 5 ++ 2 files changed, 85 insertions(+), 61 deletions(-) diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index a1ce161952fc1..217674b98c5ab 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -145,8 +145,11 @@ void ViewDialog::retranslate() populatePlanetMagnitudeAlgorithmsList(); populatePlanetMagnitudeAlgorithmDescription(); ui->lightPollutionWidget->retranslate(); + populateHipsGroups(); + clearHips(); updateHips(); + //Hack to shrink the tabs to optimal size after language change //by causing the list items to be laid out again. updateTabBarListWidgetWidth(); @@ -679,7 +682,7 @@ void ViewDialog::createDialogContent() connect(ui->surveyTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateHips())); connect(ui->stackListWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(updateHips())); - connect(ui->surveysTreeWidget, &QTreeWidget::currentItemChanged, this, &ViewDialog::updateHips, Qt::QueuedConnection); + connect(ui->surveysTreeWidget, &QTreeWidget::currentItemChanged, this, &ViewDialog::updateHipsText, Qt::QueuedConnection); connect(ui->surveysTreeWidget, &QTreeWidget::itemChanged, this, &ViewDialog::hipsListItemChanged); connect(ui->surveysFilter, &QLineEdit::textChanged, this, &ViewDialog::filterSurveys); updateHips(); @@ -757,15 +760,59 @@ static QString getHipsType(const HipsSurveyP hips) return "other"; } +void ViewDialog::updateHipsText() +{ + const auto currentItem = ui->surveysTreeWidget->currentItem(); + if (!currentItem) + { + ui->surveysTextBrowser->setText(""); + return; + } + + const auto hipsmgr = qobject_cast(StelApp::getInstance().getModule("HipsMgr")); + const auto url = currentItem->data(0, HipsRole::URL).toString(); + const auto hips = hipsmgr->getSurveyByUrl(url); + if (!hips) + { + ui->surveysTextBrowser->setText(""); + return; + } + QJsonObject props = hips->property("properties").toJsonObject(); + QString html = QString("

%1

\n").arg(props["obs_title"].toString()); + if (props.contains("obs_copyright") && props.contains("obs_copyright_url")) + { + html += QString("

Copyright %1

\n") + .arg(props["obs_copyright"].toString(), props["obs_copyright_url"].toString()); + } + html += QString("

%1

\n").arg(props["obs_description"].toString()); + html += "

" + q_("properties") + "

\n
    \n"; + for (auto iter = props.constBegin(); iter != props.constEnd(); iter++) + { + html += QString("
  • %1 %2
  • \n").arg(iter.key(), iter.value().toString()); + } + html += "
\n"; + const auto gui = dynamic_cast(StelApp::getInstance().getGui()); + if (gui) + ui->surveysTextBrowser->document()->setDefaultStyleSheet(QString(gui->getStelStyle().htmlStyleSheet)); + ui->surveysTextBrowser->setHtml(html); +} + +void ViewDialog::clearHips() +{ + ui->surveysTreeWidget->clear(); + planetarySurveys.clear(); + surveysInTheList.clear(); + selectedSurveyType.clear(); +} + void ViewDialog::updateHips() { if (!ui->page_surveys->isVisible()) return; - StelGui* gui = dynamic_cast(StelApp::getInstance().getGui()); StelModule *hipsmgr = StelApp::getInstance().getModule("HipsMgr"); QMetaObject::invokeMethod(hipsmgr, "loadSources"); QComboBox* typeComboBox = ui->surveyTypeComboBox; - QVariant selectedType = typeComboBox->itemData(typeComboBox->currentIndex()); + auto selectedType = typeComboBox->itemData(typeComboBox->currentIndex()).toString(); if (selectedType.isNull()) selectedType = "dss"; @@ -774,22 +821,22 @@ void ViewDialog::updateHips() if (!hipsmgr->property("loaded").toBool()) { - l->clear(); + clearHips(); new QTreeWidgetItem(l, {q_("Loading...")}); return; } - planetarySurveys.clear(); - - QString currentSurvey = l->currentItem() ? l->currentItem()->data(0, HipsRole::URL).toString() : ""; - QTreeWidgetItem* currentItem = nullptr; - HipsSurveyP currentHips; l->blockSignals(true); - l->clear(); + if (selectedSurveyType != selectedType) + { + clearHips(); + selectedSurveyType = selectedType; + } const QList hipslist = hipsmgr->property("surveys").value>(); const bool selectedIsPlanetary = selectedType == "sol"; l->setRootIsDecorated(selectedIsPlanetary); + const auto& solarSys = GETSTELMODULE(SolarSystem); for (auto &hips: hipslist) { @@ -798,8 +845,15 @@ void ViewDialog::updateHips() QString url = hips->property("url").toString(); QJsonObject properties = hips->property("properties").toJsonObject(); QString title = properties["obs_title"].toString(); - if (title.isEmpty()) + if (title.isEmpty() || url.isEmpty()) continue; + + // Don't add the survey if it's already present + const auto surveyItemIt = surveysInTheList.find(url); + if (surveyItemIt != surveysInTheList.end()) + continue; + + QTreeWidgetItem* surveyItem = nullptr; if (selectedIsPlanetary) { const auto& englishName = hips->getFrame(); @@ -835,63 +889,28 @@ void ViewDialog::updateHips() QString typeName = type; if (const auto it = typeNames.find(type); it != typeNames.end()) typeName = it.value(); - const auto surveyItem = new QTreeWidgetItem(groupItem, {title, typeName}); - surveyItem->setData(0, HipsRole::ItemType, HipsItemType::Survey); - surveyItem->setFlags(surveyItem->flags() | Qt::ItemIsUserCheckable); - surveyItem->setData(0, HipsRole::URL, url); - if (url == currentSurvey) - { - currentItem = surveyItem; - currentHips = hips; - groupItem->setExpanded(true); - planetData.planetItem->setExpanded(true); - } + + surveyItem = new QTreeWidgetItem(groupItem, {title, typeName}); } else { - QTreeWidgetItem* item = new QTreeWidgetItem(l, {title}); - item->setData(0, HipsRole::ItemType, HipsItemType::Survey); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); - item->setData(0, HipsRole::URL, url); - if (url == currentSurvey) - { - currentItem = item; - currentHips = hips; - } - disconnect(hips.data(), nullptr, this, nullptr); - connect(hips.data(), SIGNAL(statusChanged()), this, SLOT(updateHips())); + surveyItem = new QTreeWidgetItem(l, {title}); + surveyItem->setFlags(surveyItem->flags() | Qt::ItemIsUserCheckable); + surveyItem->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked + : Qt::Unchecked); } + + surveysInTheList[url] = surveyItem; + surveyItem->setData(0, HipsRole::ItemType, HipsItemType::Survey); + surveyItem->setData(0, HipsRole::URL, url); + disconnect(hips.data(), nullptr, this, nullptr); + connect(hips.data(), &HipsSurvey::statusChanged, this, &ViewDialog::updateHipsText); } l->sortItems(0, Qt::AscendingOrder); - l->setCurrentItem(currentItem); - l->scrollToItem(currentItem); - l->blockSignals(false); + if (const auto currentItem = l->currentItem()) + l->scrollToItem(currentItem); - if (!currentHips) - { - ui->surveysTextBrowser->setText(""); - } - else - { - QJsonObject props = currentHips->property("properties").toJsonObject(); - QString html = QString("

%1

\n").arg(props["obs_title"].toString()); - if (props.contains("obs_copyright") && props.contains("obs_copyright_url")) - { - html += QString("

Copyright %1

\n") - .arg(props["obs_copyright"].toString(), props["obs_copyright_url"].toString()); - } - html += QString("

%1

\n").arg(props["obs_description"].toString()); - html += "

" + q_("properties") + "

\n
    \n"; - for (auto iter = props.constBegin(); iter != props.constEnd(); iter++) - { - html += QString("
  • %1 %2
  • \n").arg(iter.key(), iter.value().toString()); - } - html += "
\n"; - if (gui) - ui->surveysTextBrowser->document()->setDefaultStyleSheet(QString(gui->getStelStyle().htmlStyleSheet)); - ui->surveysTextBrowser->setHtml(html); - } + l->blockSignals(false); filterSurveys(); } diff --git a/src/gui/ViewDialog.hpp b/src/gui/ViewDialog.hpp index aec738b3f8e5f..7f350cd967dc1 100644 --- a/src/gui/ViewDialog.hpp +++ b/src/gui/ViewDialog.hpp @@ -97,7 +97,9 @@ private slots: void updateSelectedCatalogsCheckBoxes(); void updateSelectedTypesCheckBoxes(); + void clearHips(); void updateHips(); + void updateHipsText(); void filterSurveys(); void hipsListItemChanged(QTreeWidgetItem* item); void populateHipsGroups(); @@ -123,6 +125,7 @@ private slots: GreatRedSpotDialog * greatRedSpotDialog; ConfigureDSOColorsDialog * configureDSOColorsDialog; ConfigureOrbitColorsDialog * configureOrbitColorsDialog; + QTimer hipsUpdateTimer; struct PlanetSurveyPack { @@ -130,6 +133,8 @@ private slots: QHash groupsMap; }; QHash planetarySurveys; + QHash surveysInTheList; + QString selectedSurveyType; }; #endif // _VIEWDIALOG_HPP From 098515a9d117f0d680803123d51c2471b5a2a056 Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sun, 17 Aug 2025 14:13:42 +0800 Subject: [PATCH 04/10] Assume that all the planetary HiPS describe color maps by default --- src/core/StelHips.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/StelHips.cpp b/src/core/StelHips.cpp index bbbdad822a044..a4489119a7f91 100644 --- a/src/core/StelHips.cpp +++ b/src/core/StelHips.cpp @@ -127,6 +127,10 @@ void HipsSurvey::checkForPlanetarySurvey() planetarySurvey = !QStringList{"equatorial","galactic","ecliptic"}.contains(hipsFrame, Qt::CaseInsensitive) || std::as_const(properties)["creator_did"].toString().contains("moon", Qt::CaseInsensitive) || std::as_const(properties)["client_category"].toString().contains("solar system", Qt::CaseInsensitive); + + // Assume that all the planetary HiPS describe color maps by default + if (planetarySurvey && type.isEmpty()) + type = "planet"; } bool HipsSurvey::isVisible() const From 600c6d6af5ee00be6ec389d15a5e87693610774b Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sun, 17 Aug 2025 18:11:05 +0800 Subject: [PATCH 05/10] Make planet surveys individually checkable --- src/gui/ViewDialog.cpp | 58 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index 217674b98c5ab..5de269694a21e 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -85,6 +85,7 @@ struct HipsRole URL = Qt::UserRole, PlanetEnglishName, ItemType, + SurveyType, }; }; @@ -890,7 +891,26 @@ void ViewDialog::updateHips() if (const auto it = typeNames.find(type); it != typeNames.end()) typeName = it.value(); + // Set the first survey of each type as checked by default + auto checkState = Qt::Checked; + for (int n = 0; n < groupItem->childCount(); ++n) + { + const auto surveyItem = groupItem->child(n); + Q_ASSERT(surveyItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Survey); + + if (surveyItem->data(0, HipsRole::SurveyType).toString() != type) + continue; + if (surveyItem->checkState(0) == Qt::Checked) + { + // A checked survey already exists, don't check the new one + checkState = Qt::Unchecked; + break; + } + } + surveyItem = new QTreeWidgetItem(groupItem, {title, typeName}); + surveyItem->setFlags(surveyItem->flags() | Qt::ItemIsUserCheckable); + surveyItem->setCheckState(0, checkState); } else { @@ -903,6 +923,7 @@ void ViewDialog::updateHips() surveysInTheList[url] = surveyItem; surveyItem->setData(0, HipsRole::ItemType, HipsItemType::Survey); surveyItem->setData(0, HipsRole::URL, url); + surveyItem->setData(0, HipsRole::SurveyType, hips->getType()); disconnect(hips.data(), nullptr, this, nullptr); connect(hips.data(), &HipsSurvey::statusChanged, this, &ViewDialog::updateHipsText); } @@ -975,17 +996,38 @@ void ViewDialog::hipsListItemChanged(QTreeWidgetItem* item) { case HipsItemType::Survey: { - QString url = item->data(0, HipsRole::URL).toString(); - const auto hips = hipsmgr->getSurveyByUrl(url); - Q_ASSERT(hips); - if (item->checkState(0) == Qt::Checked) + const auto url = item->data(0, HipsRole::URL).toString(); + if (const auto group = item->parent()) { - l->setCurrentItem(item); - hips->setProperty("visible", true); + Q_ASSERT(group->data(0, HipsRole::ItemType).toInt() == HipsItemType::Group); + for (int n = 0; n < group->childCount(); ++n) + { + const auto surveyItem = group->child(n); + Q_ASSERT(surveyItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Survey); + if (surveyItem == item) continue; + // Only consider surveys of the same type + if (surveyItem->data(0, HipsRole::SurveyType) != item->data(0, HipsRole::SurveyType)) + continue; + if (surveyItem->checkState(0) == Qt::Checked) + { + surveyItem->setCheckState(0, Qt::Unchecked); + } + } + hipsListItemChanged(group); } else { - hips->setProperty("visible", false); + const auto hips = hipsmgr->getSurveyByUrl(url); + Q_ASSERT(hips); + if (item->checkState(0) == Qt::Checked) + { + l->setCurrentItem(item); + hips->setProperty("visible", true); + } + else + { + hips->setProperty("visible", false); + } } break; } @@ -1017,6 +1059,8 @@ void ViewDialog::hipsListItemChanged(QTreeWidgetItem* item) for (int n = 0; n < item->childCount(); ++n) { const auto child = item->child(n); + if (child->checkState(0) != Qt::Checked) + continue; const auto url = child->data(0, HipsRole::URL).toString(); const auto hips = hipsmgr->getSurveyByUrl(url); Q_ASSERT(hips); From eac2d64d692f62de084b3036b2ed0a7ec6c7034c Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sun, 17 Aug 2025 18:57:35 +0800 Subject: [PATCH 06/10] Apply QListWidget style to QTreeWidget --- data/gui/normalStyle.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/gui/normalStyle.css b/data/gui/normalStyle.css index badabb3815b7a..3a0376dbfa415 100644 --- a/data/gui/normalStyle.css +++ b/data/gui/normalStyle.css @@ -55,6 +55,7 @@ QSizeGrip { QTextEdit, QPlainTextEdit, QListWidget, +QTreeWidget, QLineEdit { border: 1px solid rgb(0, 0, 0); border-radius: 0; @@ -67,6 +68,7 @@ QLineEdit { QTextEdit:disabled, QListWidget:disabled, +QTreeWidget:disabled, QLineEdit:disabled { background: rgb(90, 90, 90); } @@ -424,6 +426,7 @@ QGroupBox::indicator { * indicators, but there's a problem with spacing discussed at * https://stackoverflow.com/q/77422984 */ QListWidget::indicator, +QTreeWidget::indicator, QListView::indicator { margin: 0; background: none; @@ -431,6 +434,7 @@ QListView::indicator { QCheckBox::indicator:checked, QListWidget::indicator:checked, +QTreeWidget::indicator:checked, QListView::indicator:checked, QGroupBox::indicator:checked { image: url(:/graphicGui/uieCheckbox-checked.png); @@ -438,6 +442,7 @@ QGroupBox::indicator:checked { QCheckBox::indicator:indeterminate, QListWidget::indicator:indeterminate, +QTreeWidget::indicator:indeterminate, QListView::indicator:indeterminate, QGroupBox::indicator:indeterminate { image: url(:/graphicGui/uieCheckbox-partial.png); @@ -445,6 +450,7 @@ QGroupBox::indicator:indeterminate { QCheckBox::indicator:unchecked, QListWidget::indicator:unchecked, +QTreeWidget::indicator:unchecked, QListView::indicator:unchecked, QGroupBox::indicator:unchecked { image: url(:/graphicGui/uieCheckbox-unchecked.png); @@ -452,6 +458,7 @@ QGroupBox::indicator:unchecked { QCheckBox::indicator:hover:checked, QListWidget::indicator:hover:checked, +QTreeWidget::indicator:hover:checked, QListView::indicator:hover:checked, QGroupBox::indicator:hover:checked { image: url(:/graphicGui/uieCheckbox-checked-hover.png); @@ -459,6 +466,7 @@ QGroupBox::indicator:hover:checked { QCheckBox::indicator:hover:indeterminate, QListWidget::indicator:hover:indeterminate, +QTreeWidget::indicator:hover:indeterminate, QListView::indicator:hover:indeterminate, QGroupBox::indicator:hover:indeterminate { image: url(:/graphicGui/uieCheckbox-partial-hover.png); @@ -466,6 +474,7 @@ QGroupBox::indicator:hover:indeterminate { QCheckBox::indicator:hover:unchecked, QListWidget::indicator:hover:unchecked, +QTreeWidget::indicator:hover:unchecked, QListView::indicator:hover:unchecked, QGroupBox::indicator:hover:unchecked { image: url(:/graphicGui/uieCheckbox-unchecked-hover.png); @@ -473,6 +482,7 @@ QGroupBox::indicator:hover:unchecked { QCheckBox::indicator:checked:disabled, QListWidget::indicator:checked:disabled, +QTreeWidget::indicator:checked:disabled, QListView::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { image: url(:/graphicGui/uieCheckbox-checked-disabled.png); @@ -480,6 +490,7 @@ QGroupBox::indicator:checked:disabled { QCheckBox::indicator:indeterminate:disabled, QListWidget::indicator:indeterminate:disabled, +QTreeWidget::indicator:indeterminate:disabled, QListView::indicator:indeterminate:disabled, QGroupBox::indicator:indeterminate:disabled { image: url(:/graphicGui/uieCheckbox-partial-disabled.png); @@ -487,6 +498,7 @@ QGroupBox::indicator:indeterminate:disabled { QCheckBox::indicator:unchecked:disabled, QListWidget::indicator:unchecked:disabled, +QTreeWidget::indicator:unchecked:disabled, QListView::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { image: url(:/graphicGui/uieCheckbox-unchecked-disabled.png); From 41bb39eeb30162c3170ff2fbebf3e70c301ea69b Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Mon, 18 Aug 2025 11:23:40 +0800 Subject: [PATCH 07/10] Make sure an unchecked survey is disabled --- src/gui/ViewDialog.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index 5de269694a21e..8bcf4d8e09905 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -1059,12 +1059,16 @@ void ViewDialog::hipsListItemChanged(QTreeWidgetItem* item) for (int n = 0; n < item->childCount(); ++n) { const auto child = item->child(n); - if (child->checkState(0) != Qt::Checked) - continue; const auto url = child->data(0, HipsRole::URL).toString(); const auto hips = hipsmgr->getSurveyByUrl(url); Q_ASSERT(hips); const auto type = hips->getType(); + if (child->checkState(0) != Qt::Checked) + { + if (type == L1S("planet")) + hips->setProperty("visible", false); + continue; + } if (type == L1S("planet")) colors = hips; else if (type == L1S("planet-normal")) From 015f176583f6f73867d3773f05d47649282d248b Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Tue, 19 Aug 2025 22:06:35 +0800 Subject: [PATCH 08/10] =?UTF-8?q?Shift=20HiPS=20without=20"type"=20field?= =?UTF-8?q?=20by=20180=C2=B0=20in=20longitude?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The planetary surveys from alasky.cds.unistra.fr appear to have a reference frame that's rotated by 180° in longitude relative to that of Stellarium's planetary surveys. These surveys were created by Aladin/HipsGen, which I assume to be more or less a reference implementation. Since we can't easily change Stellarium's frame without backwards compatibility issues, and the "type" field is a custom field not used outside of Stellarium, we can use it as an indicator whether we need to fixup the coordinates of a survey. --- src/core/StelHips.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/StelHips.cpp b/src/core/StelHips.cpp index a4489119a7f91..5f2a34323ec91 100644 --- a/src/core/StelHips.cpp +++ b/src/core/StelHips.cpp @@ -56,6 +56,17 @@ static QString getExt(const QString& format) return QString(); } +static int shiftPix180deg(const int order, const int origPix) +{ + const int scale = 1 << (2 * order); + const int baseSide = origPix / scale; // 0..11 + Q_ASSERT(baseSide < 12); + const int newBaseSide = baseSide / 4 * 4 + (baseSide + 2) % 4; + const int newPix = origPix + (newBaseSide - baseSide) * scale; + Q_ASSERT(newPix >= 0); + return newPix; +} + QUrl HipsSurvey::getUrlFor(const QString& path) const { QUrl base = url; @@ -379,7 +390,8 @@ HipsTile* HipsSurvey::getTile(int order, int pix) tile->order = order; tile->pix = pix; QString ext = getExt(properties["hips_tile_format"].toString()); - QUrl path = getUrlFor(QString("Norder%1/Dir%2/Npix%3.%4").arg(order).arg((pix / 10000) * 10000).arg(pix).arg(ext)); + const int texturePix = properties["type"].toString().isEmpty() ? shiftPix180deg(order, pix) : pix; + QUrl path = getUrlFor(QString("Norder%1/Dir%2/Npix%3.%4").arg(order).arg((texturePix / 10000) * 10000).arg(texturePix).arg(ext)); const StelTexture::StelTextureParams texParams(true, GL_LINEAR, GL_CLAMP_TO_EDGE, true); tile->texture = texMgr.createTextureThread(path.url(), texParams, false); From 3e36e1df5bae0d98c0db68cbf9692a7dd99e8000 Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sat, 23 Aug 2025 01:49:04 +0800 Subject: [PATCH 09/10] Make sure visible HiPS item is checked in the tree --- src/gui/ViewDialog.cpp | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index 8bcf4d8e09905..780104020da51 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -809,7 +809,7 @@ void ViewDialog::clearHips() void ViewDialog::updateHips() { if (!ui->page_surveys->isVisible()) return; - StelModule *hipsmgr = StelApp::getInstance().getModule("HipsMgr"); + const auto hipsmgr = qobject_cast(StelApp::getInstance().getModule("HipsMgr")); QMetaObject::invokeMethod(hipsmgr, "loadSources"); QComboBox* typeComboBox = ui->surveyTypeComboBox; @@ -893,14 +893,33 @@ void ViewDialog::updateHips() // Set the first survey of each type as checked by default auto checkState = Qt::Checked; + const bool newHipsVisible = hips->property("visible").toBool(); + if (newHipsVisible) + { + // The group is also enabled if the survey being added is visible + groupItem->setCheckState(0, Qt::Checked); + } for (int n = 0; n < groupItem->childCount(); ++n) { - const auto surveyItem = groupItem->child(n); - Q_ASSERT(surveyItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Survey); + const auto oldHipsItem = groupItem->child(n); + Q_ASSERT(oldHipsItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Survey); - if (surveyItem->data(0, HipsRole::SurveyType).toString() != type) + if (oldHipsItem->data(0, HipsRole::SurveyType).toString() != type) continue; - if (surveyItem->checkState(0) == Qt::Checked) + bool oldHipsItemChecked = oldHipsItem->checkState(0) == Qt::Checked; + if (newHipsVisible && oldHipsItemChecked) + { + const auto url = oldHipsItem->data(0, HipsRole::URL).toString(); + const auto oldHips = hipsmgr->getSurveyByUrl(url); + Q_ASSERT(oldHips); + if (oldHips && !oldHips->property("visible").toBool()) + { + checkState = Qt::Checked; + oldHipsItem->setCheckState(0, Qt::Unchecked); + oldHipsItemChecked = false; + } + } + if (!newHipsVisible && oldHipsItemChecked) { // A checked survey already exists, don't check the new one checkState = Qt::Unchecked; From 085278ebcfc8e2965784e62caf231e2d74b1a3f0 Mon Sep 17 00:00:00 2001 From: Ruslan Kabatsayev Date: Sat, 23 Aug 2025 13:40:02 +0800 Subject: [PATCH 10/10] Special-case (1) Ceres to be findable from HiPS frame --- src/core/StelHips.cpp | 7 +++++++ src/core/StelHips.hpp | 2 ++ src/core/modules/SolarSystem.cpp | 2 +- src/gui/ViewDialog.cpp | 5 +++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core/StelHips.cpp b/src/core/StelHips.cpp index 5f2a34323ec91..3e12c46f68ea9 100644 --- a/src/core/StelHips.cpp +++ b/src/core/StelHips.cpp @@ -133,6 +133,13 @@ HipsSurvey::~HipsSurvey() { } +QString HipsSurvey::frameToPlanetName(const QString& frame) +{ + if (frame == "ceres") + return "(1) ceres"; + return frame; +} + void HipsSurvey::checkForPlanetarySurvey() { planetarySurvey = !QStringList{"equatorial","galactic","ecliptic"}.contains(hipsFrame, Qt::CaseInsensitive) || diff --git a/src/core/StelHips.hpp b/src/core/StelHips.hpp index 6c3be047b4362..e4a83353e0df6 100644 --- a/src/core/StelHips.hpp +++ b/src/core/StelHips.hpp @@ -69,6 +69,8 @@ class HipsSurvey : public QObject const QMap& hipslistProps, double releaseDate=0.0); ~HipsSurvey() override; + static QString frameToPlanetName(const QString& frame); + //! Get whether the survey is visible. bool isVisible() const; diff --git a/src/core/modules/SolarSystem.cpp b/src/core/modules/SolarSystem.cpp index 7e227495bd8a5..82fbd65dd0b45 100644 --- a/src/core/modules/SolarSystem.cpp +++ b/src/core/modules/SolarSystem.cpp @@ -4199,7 +4199,7 @@ const QMap SolarSystem::vMagAlgorit void SolarSystem::enableSurvey(const HipsSurveyP& colors, const HipsSurveyP& normals, const HipsSurveyP& horizons) { Q_ASSERT(colors); - QString planetName = colors->getFrame(); + QString planetName = HipsSurvey::frameToPlanetName(colors->getFrame()); PlanetP pl = searchByEnglishName(planetName); if (!pl) return; diff --git a/src/gui/ViewDialog.cpp b/src/gui/ViewDialog.cpp index 780104020da51..ae73e8ef0d81e 100644 --- a/src/gui/ViewDialog.cpp +++ b/src/gui/ViewDialog.cpp @@ -857,7 +857,8 @@ void ViewDialog::updateHips() QTreeWidgetItem* surveyItem = nullptr; if (selectedIsPlanetary) { - const auto& englishName = hips->getFrame(); + const auto& frame = hips->getFrame(); + const auto& englishName = hips->frameToPlanetName(frame); const auto& group = hips->getGroup(); auto& planetData = planetarySurveys[englishName]; if(!planetData.planetItem) @@ -866,7 +867,7 @@ void ViewDialog::updateHips() if (!planet) { qWarning().nospace() << "Found a planetary survey with unknown frame: " - << englishName << ". Skipping it."; + << frame << ". Skipping it."; continue; } planetData.planetItem = new QTreeWidgetItem(l, {planet->getNameI18n()});