Skip to content

Commit cd3ba1f

Browse files
Python Help Window Feature Addition - Indicator / UI element for Offline vs. Online (ornl-next merge) (#39183)
* OFFLINE_MODE * Change red to green for Local Docs text. * Updates based on comments left. * Updates to icons and comment fix * Fix logger defnition. * Release documentation * Updates to release documentation * Hopefully this fixes the issue with docs build? * Fix mantidworkbench.rst * Move conda build tools to base environment (#39177) * Move build tools to conda base environment in package-conda At least one of these dependencies causes conda to be installed, which should only be done inside the base environment. * Move conda build tool to base env in package-standalone conda build tools bring in conda as a dependency, so should only be installed inside the base environment. Also, I think conda-index is the correct dependency here. --------- Co-authored-by: thomashampson <thomas.hampson@stfc.ac.uk>
1 parent b6636e8 commit cd3ba1f

File tree

10 files changed

+403
-110
lines changed

10 files changed

+403
-110
lines changed

buildconfig/Jenkins/Conda/package-conda

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ done
8282

8383

8484
# Mamba
85-
setup_mamba $WORKSPACE/mambaforge "package-conda" true
86-
# CMake FindPython doesn't work correctly with conda 25.3.0
87-
mamba install --yes boa conda-verify versioningit conda=25.1.1
85+
setup_mamba $WORKSPACE/mambaforge "" true
86+
mamba activate base
87+
mamba install --yes boa conda-verify versioningit
8888

8989
# Clean up any legacy conda-recipes git clones
9090
cd $WORKSPACE

buildconfig/Jenkins/Conda/package-standalone

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ do
5959
done
6060

6161
# Mamba
62-
setup_mamba $WORKSPACE/mambaforge "package-standalone"
63-
# Pin conda to known working version. In 24.9 the post-link.bat script doesn't work.
64-
mamba install --yes mamba=1.5 conda-build conda=24.7
62+
setup_mamba $WORKSPACE/mambaforge "" true
63+
mamba activate base
64+
mamba install --yes conda-index
6565

6666
# Build packages
6767
# Setup a local conda channel to find our locally built packages package. It is
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- Makes the large offline documentation optional rather than a mandatory install, reducing installer/download size significantly.
2+
- Improvements:
3+
- For users who frequently access online docs or have bandwidth constraints, this saves considerable disk space (potentially hundreds of MB).
4+
- Those who prefer local/offline usage can still opt to install the documentation package and continue working without internet access.
5+
- Key benefits:
6+
- Greater flexibility in how Mantid is set up — you choose whether to save space or have fully locally built docs.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- Introduced a prototype "side-by-side" help system that includes both the legacy QtHelp-based viewer and a new Python-based Help Window using an embedded web browser (QWebEngine) to display documentation within Mantid Workbench.
2+
- Improvements:
3+
- Enhances the visual appearance and usability of in-app documentation.
4+
- Supports richer HTML content and modern formatting, including MathJax for rendering mathematical equations.
5+
- Delivers a smoother and more consistent experience when navigating help and reference material.
6+
- Key benefits:
7+
- Improved clarity for technical content (e.g. math and tables), more attractive and readable pages, and future potential for interactive elements in documentation.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- Adds a clear indicator in the Help Window’s toolbar showing whether Mantid is displaying **Local Docs** or **Online Docs** documentation.
2+
- Improvements:
3+
- Makes it obvious if you are using locally installed documentation or viewing updated online docs (default).
4+
- Helps diagnose connection or installation issues if pages are not loading as expected.
5+
- Key benefits:
6+
- Immediate clarity on where help content is being retrieved from, removing guesswork.

qt/python/mantidqt/mantidqt/widgets/helpwindow/helpwindowbridge.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,34 @@
66
# SPDX - License - Identifier: GPL - 3.0 +
77
import os
88
import sys
9+
import argparse
910

1011
from qtpy.QtWidgets import QApplication
1112
from mantidqt.widgets.helpwindow.helpwindowpresenter import HelpWindowPresenter
1213

1314
_presenter = None
1415

1516

16-
def show_help_page(relative_url, local_docs=None, online_base_url="https://docs.mantidproject.org/"):
17+
def show_help_page(relativeUrl, localDocs=None, onlineBaseUrl="https://docs.mantidproject.org/"):
1718
"""
1819
Show the help window at the given relative URL path.
1920
"""
2021
global _presenter
2122
if _presenter is None:
2223
# Create a Presenter once. Re-use it on subsequent calls.
23-
_presenter = HelpWindowPresenter(local_docs=local_docs, online_base_url=online_base_url)
24+
_presenter = HelpWindowPresenter(localDocs=localDocs, onlineBaseUrl=onlineBaseUrl)
2425

2526
# Ask the Presenter to load the requested page
26-
_presenter.showHelpPage(relative_url)
27+
_presenter.show_help_page(relativeUrl)
2728

2829

2930
def main(cmdargs=sys.argv):
3031
"""
3132
Run this script standalone to test the Python-based Help Window.
3233
"""
33-
import argparse
34-
3534
parser = argparse.ArgumentParser(description="Standalone test of the Python-based Mantid Help Window.")
3635
parser.add_argument(
37-
"relative_url", nargs="?", default="", help="Relative doc path (e.g. 'algorithms/Load-v1.html'), defaults to 'index.html' if empty."
36+
"relativeUrl", nargs="?", default="", help="Relative doc path (e.g. 'algorithms/Load-v1.html'), defaults to 'index.html' if empty."
3837
)
3938
parser.add_argument("--local-docs", default=None, help="Path to local Mantid HTML docs. Overrides environment if set.")
4039
parser.add_argument(
@@ -51,7 +50,7 @@ def main(cmdargs=sys.argv):
5150
app = QApplication(sys.argv)
5251

5352
# Show the requested help page
54-
show_help_page(relative_url=args.relative_url, local_docs=args.local_docs, online_base_url=args.online_base_url)
53+
show_help_page(relativeUrl=args.relativeUrl, localDocs=args.local_docs, onlineBaseUrl=args.online_base_url)
5554

5655
sys.exit(app.exec_())
5756

qt/python/mantidqt/mantidqt/widgets/helpwindow/helpwindowmodel.py

Lines changed: 113 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,39 @@
55
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
66
# SPDX - License - Identifier: GPL - 3.0 +
77
import os
8+
9+
import logging
810
from qtpy.QtCore import QUrl
911
from qtpy.QtWebEngineCore import QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestInfo
1012

13+
# Module-level logger for functions outside of classes
14+
_logger = logging.getLogger(__name__)
15+
16+
17+
def _get_version_string_for_url():
18+
"""
19+
Returns the Mantid version string formatted for use in documentation URLs.
20+
For example, "v6.12.0" from version "6.12.0.1"
21+
22+
Returns:
23+
str: Formatted version string in the form "vX.Y.Z" or None if version cannot be determined
24+
"""
25+
versionStr = None
26+
try:
27+
import mantid
28+
29+
# Use the mantid version object (proper way)
30+
versionObj = mantid.version()
31+
# Retrieve the patch
32+
patch = versionObj.patch.split(".")[0]
33+
versionStr = f"v{versionObj.major}.{versionObj.minor}.{patch}"
34+
except ImportError:
35+
_logger.warning("Could not determine Mantid version for documentation URL.")
36+
except Exception as e:
37+
_logger.warning(f"Error determining Mantid version for documentation URL: {e}")
38+
39+
return versionStr
40+
1141

1242
class NoOpRequestInterceptor(QWebEngineUrlRequestInterceptor):
1343
"""
@@ -31,50 +61,110 @@ def interceptRequest(self, info: QWebEngineUrlRequestInfo):
3161

3262

3363
class HelpWindowModel:
34-
def __init__(self, local_docs_base=None, online_base="https://docs.mantidproject.org/"):
35-
if local_docs_base and not os.path.isdir(local_docs_base):
36-
raise ValueError(f"Local docs directory '{local_docs_base}' does not exist or is invalid.")
37-
self.local_docs_base = local_docs_base
38-
self.online_base = online_base.rstrip("/")
64+
_logger = logging.getLogger(__name__)
65+
66+
MODE_LOCAL = "Local Docs"
67+
MODE_ONLINE = "Online Docs"
68+
69+
def __init__(self, localDocsBase=None, onlineBase="https://docs.mantidproject.org/"):
70+
self._rawLocalDocsBase = localDocsBase
71+
self._rawOnlineBase = onlineBase.rstrip("/")
72+
73+
self._isLocal = False
74+
self._modeString = self.MODE_ONLINE
75+
self._baseUrl = self._rawOnlineBase
76+
self._versionString = None
3977

40-
def is_local_docs_enabled(self):
78+
self._determine_mode_and_base_url()
79+
80+
def _determine_mode_and_base_url(self):
4181
"""
42-
:return: True if local_docs_base is set and is a valid directory
82+
Sets the internal state (_isLocal, _modeString, _baseWrl, _versionString)
83+
based on the validity of localDocsBase and attempts to find a versioned URL.
4384
"""
44-
return bool(self.local_docs_base and os.path.isdir(self.local_docs_base))
85+
if self._rawLocalDocsBase and os.path.isdir(self._rawLocalDocsBase):
86+
self._isLocal = True
87+
self._modeString = self.MODE_LOCAL
88+
absLocalPath = os.path.abspath(self._rawLocalDocsBase)
89+
self._baseUrl = QUrl.fromLocalFile(absLocalPath).toString()
90+
self._versionString = None
91+
self._logger.debug(f"Using {self._modeString} from {self._baseUrl}")
92+
else:
93+
if self._rawLocalDocsBase:
94+
self._logger.warning(f"Local docs path '{self._rawLocalDocsBase}' is invalid or not found. Falling back to online docs.")
95+
96+
self._isLocal = False
97+
self._modeString = self.MODE_ONLINE
4598

46-
def build_help_url(self, relative_url):
99+
isLikelyRelease = self._rawLocalDocsBase is None
100+
if isLikelyRelease:
101+
self._versionString = _get_version_string_for_url()
102+
103+
if self._versionString:
104+
baseOnline = self._rawOnlineBase
105+
if baseOnline.endswith("/stable"):
106+
baseOnline = baseOnline[: -len("/stable")]
107+
108+
if self._versionString not in baseOnline:
109+
self._baseUrl = f"{baseOnline.rstrip('/')}/{self._versionString}"
110+
self._logger.debug(f"Using {self._modeString} (Version: {self._versionString}) from {self._baseUrl}")
111+
else:
112+
self._baseUrl = self._rawOnlineBase
113+
self._logger.debug(f"Using {self._modeString} (Using provided base URL, possibly stable/latest): {self._baseUrl}")
114+
else:
115+
self._baseUrl = self._rawOnlineBase
116+
self._logger.debug(f"Using {self._modeString} (Version: Unknown/Stable) from {self._baseUrl}")
117+
118+
def is_local_docs_mode(self):
47119
"""
48-
Returns a QUrl pointing to either local or online docs for the given relative URL.
120+
:return: True if using local docs, False otherwise. Based on initial check.
49121
"""
50-
if not relative_url or not relative_url.endswith(".html"):
51-
relative_url = "index.html"
122+
return self._isLocal
52123

53-
if self.is_local_docs_enabled():
54-
full_path = os.path.join(self.local_docs_base, relative_url)
55-
return QUrl.fromLocalFile(full_path)
56-
else:
57-
return QUrl(self.online_base + "/" + relative_url)
124+
def get_mode_string(self):
125+
"""
126+
:return: User-friendly string indicating the mode ("Local Docs" or "Online Docs").
127+
"""
128+
return self._modeString
129+
130+
def get_base_url(self):
131+
"""`
132+
:return: The determined base URL (either file:///path or https://docs...[/version])
133+
"""
134+
return self._baseUrl.rstrip("/") + "/"
135+
136+
def build_help_url(self, relativeUrl):
137+
"""
138+
Returns a QUrl pointing to the determined doc source for the given relative URL.
139+
"""
140+
if not relativeUrl or not relativeUrl.lower().endswith((".html", ".htm")):
141+
relativeUrl = "index.html"
142+
143+
relativeUrl = relativeUrl.lstrip("/")
144+
fullUrlStr = f"{self.get_base_url()}{relativeUrl}"
145+
146+
url = QUrl(fullUrlStr)
147+
if not url.isValid():
148+
self._logger.warning(f"Constructed invalid URL: {fullUrlStr} from base '{self.get_base_url()}' and relative '{relativeUrl}'")
149+
return url
58150

59151
def get_home_url(self):
60152
"""
61153
Return the 'home' page URL:
62154
- local 'index.html' if local docs are enabled
63155
- online docs homepage otherwise
64156
"""
65-
if self.is_local_docs_enabled():
66-
full_path = os.path.join(self.local_docs_base, "index.html")
67-
return QUrl.fromLocalFile(full_path)
68-
else:
69-
return QUrl(self.online_base + "/index.html")
157+
return self.build_help_url("index.html")
70158

71159
def create_request_interceptor(self):
72160
"""
73161
Return an appropriate request interceptor:
74162
- LocalRequestInterceptor if local docs are used (for mathjax CORS)
75163
- NoOpRequestInterceptor otherwise
76164
"""
77-
if self.is_local_docs_enabled():
165+
if self._isLocal:
166+
self._logger.debug("Using LocalRequestInterceptor.")
78167
return LocalRequestInterceptor()
79168
else:
169+
self._logger.debug("Using NoOpRequestInterceptor.")
80170
return NoOpRequestInterceptor()

0 commit comments

Comments
 (0)