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); diff --git a/src/core/StelHips.cpp b/src/core/StelHips.cpp index 7b4899d00d6ae..3e12c46f68ea9 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; @@ -66,9 +77,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_), @@ -121,11 +133,22 @@ 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) || 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 @@ -219,6 +242,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 +255,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) @@ -368,7 +397,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); @@ -675,7 +705,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 +715,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 +746,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..e4a83353e0df6 100644 --- a/src/core/StelHips.hpp +++ b/src/core/StelHips.hpp @@ -60,13 +60,17 @@ 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; + static QString frameToPlanetName(const QString& frame); + //! Get whether the survey is visible. bool isVisible() const; @@ -91,6 +95,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 +107,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 +120,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..82fbd65dd0b45 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 = HipsSurvey::frameToPlanetName(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 2742fc0592ac5..ae73e8ef0d81e 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,27 @@ struct Page }; }; +struct HipsRole +{ + enum + { + URL = Qt::UserRole, + PlanetEnglishName, + ItemType, + SurveyType, + }; +}; + +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) @@ -123,8 +146,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(); @@ -167,7 +193,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 +683,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::updateHipsText, Qt::QueuedConnection); + connect(ui->surveysTreeWidget, &QTreeWidget::itemChanged, this, &ViewDialog::hipsListItemChanged); connect(ui->surveysFilter, &QLineEdit::textChanged, this, &ViewDialog::filterSurveys); updateHips(); @@ -727,44 +753,92 @@ 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"; } +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"); + const auto hipsmgr = qobject_cast(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"; // Update survey list. - QListWidget* l = ui->surveysListWidget; + auto*const l = ui->surveysTreeWidget; if (!hipsmgr->property("loaded").toBool()) { - l->clear(); - new QListWidgetItem(q_("Loading..."), l); + clearHips(); + new QTreeWidgetItem(l, {q_("Loading...")}); return; } - QString currentSurvey = l->currentItem() ? l->currentItem()->data(Qt::UserRole).toString() : ""; - QListWidgetItem* 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) { if (getHipsType(hips) != selectedType) @@ -772,49 +846,112 @@ 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; - QListWidgetItem* item = new QListWidgetItem(title, l); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(hips->property("visible").toBool() ? Qt::Checked : Qt::Unchecked); - item->setData(Qt::UserRole, url); - if (url == currentSurvey) - { - currentItem = item; - currentHips = hips; - } - disconnect(hips.data(), nullptr, this, nullptr); - connect(hips.data(), SIGNAL(statusChanged()), this, SLOT(updateHips())); - } - l->sortItems(Qt::AscendingOrder); - l->setCurrentItem(currentItem); - l->scrollToItem(currentItem); - l->blockSignals(false); - 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")) + // 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) { - html += QString("

Copyright %1

\n") - .arg(props["obs_copyright"].toString(), props["obs_copyright_url"].toString()); + const auto& frame = hips->getFrame(); + const auto& englishName = hips->frameToPlanetName(frame); + 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: " + << frame << ". 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(); + + // 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 oldHipsItem = groupItem->child(n); + Q_ASSERT(oldHipsItem->data(0, HipsRole::ItemType).toInt() == HipsItemType::Survey); + + if (oldHipsItem->data(0, HipsRole::SurveyType).toString() != type) + continue; + 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; + break; + } + } + + surveyItem = new QTreeWidgetItem(groupItem, {title, typeName}); + surveyItem->setFlags(surveyItem->flags() | Qt::ItemIsUserCheckable); + surveyItem->setCheckState(0, checkState); } - html += QString("

%1

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

" + q_("properties") + "

\n
    \n"; - for (auto iter = props.constBegin(); iter != props.constEnd(); iter++) + else { - html += QString("
  • %1 %2
  • \n").arg(iter.key(), iter.value().toString()); + surveyItem = new QTreeWidgetItem(l, {title}); + surveyItem->setFlags(surveyItem->flags() | Qt::ItemIsUserCheckable); + surveyItem->setCheckState(0, hips->property("visible").toBool() ? Qt::Checked + : Qt::Unchecked); } - html += "
\n"; - if (gui) - ui->surveysTextBrowser->document()->setDefaultStyleSheet(QString(gui->getStelStyle().htmlStyleSheet)); - ui->surveysTextBrowser->setHtml(html); + + 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); } + l->sortItems(0, Qt::AscendingOrder); + if (const auto currentItem = l->currentItem()) + l->scrollToItem(currentItem); + + l->blockSignals(false); filterSurveys(); } @@ -859,35 +996,131 @@ 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(); - 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) + const auto hipsmgr = qobject_cast(StelApp::getInstance().getModule("HipsMgr")); + + switch (item->data(0, HipsRole::ItemType).toInt()) + { + case HipsItemType::Survey: { - l->setCurrentItem(item); - hips->setProperty("visible", true); + const auto url = item->data(0, HipsRole::URL).toString(); + if (const auto group = item->parent()) + { + 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 + { + 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: + { + // 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 (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")) + 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: { - hips->setProperty("visible", false); + break; } + } + l->blockSignals(false); } diff --git a/src/gui/ViewDialog.hpp b/src/gui/ViewDialog.hpp index 14ae74da200df..7f350cd967dc1 100644 --- a/src/gui/ViewDialog.hpp +++ b/src/gui/ViewDialog.hpp @@ -24,9 +24,11 @@ #include #include +#include class Ui_viewDialogForm; class QListWidgetItem; +class QTreeWidgetItem; class QToolButton; class AddRemoveLandscapesDialog; @@ -95,9 +97,11 @@ private slots: void updateSelectedCatalogsCheckBoxes(); void updateSelectedTypesCheckBoxes(); + void clearHips(); void updateHips(); + void updateHipsText(); void filterSurveys(); - void hipsListItemChanged(QListWidgetItem* item); + void hipsListItemChanged(QTreeWidgetItem* item); void populateHipsGroups(); void toggleHipsDialog(); @@ -121,7 +125,16 @@ private slots: GreatRedSpotDialog * greatRedSpotDialog; ConfigureDSOColorsDialog * configureDSOColorsDialog; ConfigureOrbitColorsDialog * configureOrbitColorsDialog; + QTimer hipsUpdateTimer; + struct PlanetSurveyPack + { + QTreeWidgetItem* planetItem = nullptr; + QHash groupsMap; + }; + QHash planetarySurveys; + QHash surveysInTheList; + QString selectedSurveyType; }; #endif // _VIEWDIALOG_HPP 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 + + + + + + + +