From 0af623f85618b7e84bd0686d1ee93044645e66e4 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 2 Jul 2025 19:06:41 +0200 Subject: [PATCH] this adds invisible anchors with ids like qml_controlName also attempts to add code that will scroll to the helpelement when selected. however, the jumping there does not work, and the helpoutput resets the openable sections anyway (which would be required for visible/invisble thing) this is an attempt to do https://github.com/jasp-stats/INTERNAL-jasp/issues/2766 --- Desktop/analysis/analysis.cpp | 9 ++++- Desktop/analysis/analysis.h | 1 + Desktop/utilities/helpmodel.cpp | 45 ++++++++++++++++++++++--- Desktop/utilities/helpmodel.h | 17 +++++++--- QMLComponents/analysisform.cpp | 4 ++- QMLComponents/analysisform.h | 1 + QMLComponents/controls/comboboxbase.cpp | 5 +-- QMLComponents/controls/jaspcontrol.cpp | 30 +++++++++++++++-- QMLComponents/controls/jaspcontrol.h | 3 ++ 9 files changed, 98 insertions(+), 17 deletions(-) diff --git a/Desktop/analysis/analysis.cpp b/Desktop/analysis/analysis.cpp index c5fad62046..583cda81fd 100644 --- a/Desktop/analysis/analysis.cpp +++ b/Desktop/analysis/analysis.cpp @@ -27,6 +27,7 @@ #include "utilities/qutils.h" #include "utilities/settings.h" #include "utilities/reporter.h" +#include "utilities/helpmodel.h" #include "gui/preferencesmodel.h" #include "results/resultsjsinterface.h" #include "utilities/messageforwarder.h" @@ -357,7 +358,8 @@ void Analysis::createForm(QQuickItem* parentItem) connect(this, &Analysis::titleChanged, _analysisForm, &AnalysisForm::titleChanged ); connect(this, &Analysis::needsRefreshChanged, _analysisForm, &AnalysisForm::needsRefreshChanged ); connect(this, &Analysis::needsRefreshChanged, _analysisForm, &AnalysisForm::rSyntaxTextChanged ); - connect(this, &Analysis::boundValuesChanged, this, &Analysis::setRSyntaxTextInResult, Qt::QueuedConnection ); + connect(this, &Analysis::boundValuesChanged, this, &Analysis::setRSyntaxTextInResult, Qt::QueuedConnection ); + connect(_analysisForm, &AnalysisForm::helpJumpToAnchor, this, &Analysis::helpJumpToAnchor, Qt::QueuedConnection ); setRSyntaxTextInResult(); _analysisForm->setShowRButton(_moduleData->hasWrapper()); @@ -1086,6 +1088,11 @@ void Analysis::onUsedVariablesChanged() DataSetPackage::pkg()->checkComputedColumnDependenciesForAnalysis(this); } +void Analysis::helpJumpToAnchor(const QString &anchor) +{ + HelpModel::singleton()->jumpToAnchor(anchor); +} + void Analysis::checkForRSources() { if(!_results.isMember(".meta")) diff --git a/Desktop/analysis/analysis.h b/Desktop/analysis/analysis.h index bc8847f9c9..f9be79ac51 100644 --- a/Desktop/analysis/analysis.h +++ b/Desktop/analysis/analysis.h @@ -198,6 +198,7 @@ public slots: void setRSyntaxTextInResult(); void filterByNameDone(const QString &name, const QString &error); void onUsedVariablesChanged() override; + void helpJumpToAnchor(const QString & anchor); protected: void abort(); diff --git a/Desktop/utilities/helpmodel.cpp b/Desktop/utilities/helpmodel.cpp index 979afd684a..90fd6db61b 100644 --- a/Desktop/utilities/helpmodel.cpp +++ b/Desktop/utilities/helpmodel.cpp @@ -7,8 +7,13 @@ #include "gui/preferencesmodel.h" #include "log.h" +HelpModel * HelpModel::_singleton = nullptr; + HelpModel::HelpModel(QObject * parent) : QObject(parent) { + assert(!_singleton); + _singleton = this; + setPagePath("index"); connect(this, &HelpModel::pagePathChanged, this, &HelpModel::generateJavascript); connect(PreferencesModel::prefs(), &PreferencesModel::currentThemeNameChanged, this, &HelpModel::setThemeCss, Qt::QueuedConnection); @@ -33,6 +38,8 @@ void HelpModel::runJavaScript(QString renderFunc, QString content) runJavaScriptSignal(renderFunc + "(\"" + content + "\");"); } + + void HelpModel::setVisible(bool visible) { if (_visible == visible) @@ -58,7 +65,7 @@ void HelpModel::loadingSucceeded() generateJavascript(); } -void HelpModel::setMarkdown(QString markdown) +void HelpModel::setMarkdown(const QString & markdown) { if (_markdown == markdown) return; @@ -76,8 +83,12 @@ void HelpModel::setMarkdown(QString markdown) void HelpModel::setPagePath(QString pagePath) { - _pagePath = pagePath; - emit pagePathChanged(_pagePath); + if(_pagePath != pagePath) + _anchorName = ""; + + _pagePath = pagePath; + + emit pagePathChanged(HelpModel::pagePath()); } QString HelpModel::indexURL() @@ -157,6 +168,8 @@ void HelpModel::showOrToggleParticularPageForAnalysis(Analysis * analysis, QStri if(analysis == _analysis && pagePath == _pagePath && _visible) { setVisible(false); + _anchorName = ""; + return; } else @@ -205,7 +218,7 @@ void HelpModel::setFont() } ///Temporary function for https://github.com/jasp-stats/INTERNAL-jasp/issues/1215 -bool HelpModel::pageExists(QString pagePath) +bool HelpModel::pageExists(const QString & pagePath) { QString renderFunc, content; @@ -214,6 +227,26 @@ bool HelpModel::pageExists(QString pagePath) return loadHelpContent(pagePath, false, renderFunc, content) || loadHelpContent(pagePath, true, renderFunc, content); } +void HelpModel::jumpToAnchor(const QString &anchorName) +{ + _anchorName = anchorName; + + emit pagePathChanged(pagePath()); + + jumpToSelectedAnchor(); +} + +void HelpModel::jumpToSelectedAnchor() +{ + if(!_anchorName.isEmpty()) + emit runJavaScriptSignal(QString("document.getElementById('%1').scrollIntoView()").arg(_anchorName)); +} + +QString HelpModel::pagePath() const +{ + return _anchorName.isEmpty() ? _pagePath : _pagePath + "#" + _anchorName; +} + bool HelpModel::loadHelpContent(const QString & pagePath, bool ignorelanguage, QString &renderFunc, QString &content) { @@ -262,12 +295,14 @@ bool HelpModel::loadHelpContent(const QString & pagePath, bool ignorelanguage, Q return found; } -void HelpModel::loadMarkdown(QString md) +void HelpModel::loadMarkdown(const QString & md) { //Log::log() << "loadMarkdown got:\n" << md << std::endl; setVisible(true); runJavaScript("window.render", md); + + jumpToSelectedAnchor(); } void HelpModel::setAnalysis(Analysis *newAnalysis) diff --git a/Desktop/utilities/helpmodel.h b/Desktop/utilities/helpmodel.h index 551f6ba340..fefe15279d 100644 --- a/Desktop/utilities/helpmodel.h +++ b/Desktop/utilities/helpmodel.h @@ -24,9 +24,11 @@ class HelpModel : public QObject void runJavaScript(QString renderFunc, QString content); bool visible() const { return _visible; } - QString pagePath() const { return _pagePath; } + QString pagePath() const; QString markdown() const { return _markdown; } Analysis * analysis() const { return _analysis; } + + static HelpModel * singleton() { return _singleton; } public slots: void setVisible(bool visible); @@ -42,9 +44,11 @@ public slots: void setThemeCss(QString themeName); void setFont(); void loadingSucceeded(); - void setMarkdown(QString markdown); - void loadMarkdown(QString md); - bool pageExists(QString pagePath); + void setMarkdown( const QString & markdown); + void loadMarkdown( const QString & md); + bool pageExists( const QString & pagePath); + void jumpToAnchor( const QString & anchorName); + void jumpToSelectedAnchor(); signals: void renderCode(QString javascript); @@ -61,8 +65,11 @@ public slots: private: bool _visible = false; QString _pagePath = "", - _markdown = ""; + _markdown = "", + _anchorName = ""; Analysis * _analysis = nullptr; + + static HelpModel * _singleton; }; #endif // HELPMODEL_H diff --git a/QMLComponents/analysisform.cpp b/QMLComponents/analysisform.cpp index a1f71eacf5..cc45e030f5 100644 --- a/QMLComponents/analysisform.cpp +++ b/QMLComponents/analysisform.cpp @@ -334,7 +334,9 @@ void AnalysisForm::_setUp() for (JASPControl* control : controls) { _dependsOrderedCtrls.push_back(control); - connect(control, &JASPControl::helpMDChanged, this, &AnalysisForm::helpMDChanged); + connect(control, &JASPControl::helpMDChanged, this, &AnalysisForm::helpMDChanged); + connect(control, &JASPControl::helpJumpToAnchor, this, &AnalysisForm::helpJumpToAnchor); + } _rSyntax->setUp(); diff --git a/QMLComponents/analysisform.h b/QMLComponents/analysisform.h index 091c4770cc..535cf7e885 100644 --- a/QMLComponents/analysisform.h +++ b/QMLComponents/analysisform.h @@ -118,6 +118,7 @@ public slots: void infoChanged(); void infoBottomChanged(); void helpMDChanged(); + void helpJumpToAnchor(const QString & anchor); void errorsChanged(); void warningsChanged(); void analysisChanged(); diff --git a/QMLComponents/controls/comboboxbase.cpp b/QMLComponents/controls/comboboxbase.cpp index 2c85dacc24..d97514d7c9 100644 --- a/QMLComponents/controls/comboboxbase.cpp +++ b/QMLComponents/controls/comboboxbase.cpp @@ -310,8 +310,9 @@ QString ComboBoxBase::generateMDHelp(int depth) const { QStringList markdown; - markdown << printLabelMD(depth); - markdown << info(); + markdown << markdownAnchor() + << printLabelMD(depth) + << info(); // If one of the option has an info property, then display the options as an unordered list if (_hasOptionInfo()) diff --git a/QMLComponents/controls/jaspcontrol.cpp b/QMLComponents/controls/jaspcontrol.cpp index e0bb5b1918..693b7bac03 100644 --- a/QMLComponents/controls/jaspcontrol.cpp +++ b/QMLComponents/controls/jaspcontrol.cpp @@ -60,6 +60,7 @@ JASPControl::JASPControl(QQuickItem *parent) : QQuickItem(parent) connect(this, &JASPControl::debugChanged, [this] () { _setBackgroundColor(); _setVisible(); } ); connect(this, &JASPControl::parentDebugChanged, [this] () { _setBackgroundColor(); _setVisible(); } ); connect(this, &JASPControl::boundValueChanged, this, &JASPControl::_resetBindingValue); + connect(this, &JASPControl::activeFocusChanged, this, &JASPControl::_handleActiveFocusChanged); connect(this, &JASPControl::activeFocusChanged, this, &JASPControl::_setFocus); connect(this, &JASPControl::activeFocusChanged, this, &JASPControl::_notifyFormOfActiveFocus); @@ -496,6 +497,12 @@ void JASPControl::_checkControlName() { checkOptionName(_name); } + +void JASPControl::_handleActiveFocusChanged() +{ + if(hasActiveFocus() && name() != "") + emit helpJumpToAnchor(markdownAnchor(false)); + } bool JASPControl::checkOptionName(const QString &name) { @@ -631,12 +638,28 @@ JASPControls JASPControl::getMDSubItems(const QQuickItem* parentItem) const return MDSubItems; } + +QString JASPControl::markdownAnchor(bool includeHtml) const +{ + if(_name == "") + return ""; + + const QString anchorName = "qml_" + name(); + if(!includeHtml) + return anchorName; + + return QString("").arg(anchorName); +} QString JASPControl::generateMDHelp(int depth) const { - JASPControls MDSubItems = getMDSubItems(); - QStringList markdown; - markdown << printLabelMD(depth) << info() << "\n"; + JASPControls MDSubItems = getMDSubItems(); + QStringList markdown; + + markdown << markdownAnchor() + << printLabelMD(depth) + << info() + << "\n"; if (MDSubItems.size() > 0) { @@ -698,6 +721,7 @@ void JASPControl::setName(const QString &name) { _name = name; emit nameChanged(); + emit helpMDChanged(); } } diff --git a/QMLComponents/controls/jaspcontrol.h b/QMLComponents/controls/jaspcontrol.h index 573da192e9..d5f0581c62 100644 --- a/QMLComponents/controls/jaspcontrol.h +++ b/QMLComponents/controls/jaspcontrol.h @@ -119,6 +119,7 @@ class JASPControl : public QQuickItem virtual bool infoLabelItalic() const { return false; } QString toolTip() const { return _toolTip; } + QString markdownAnchor(bool includeHtml=true) const; ///