mantidproject / mantid

Main repository for Mantid code
https://www.mantidproject.org
GNU General Public License v3.0
209 stars 122 forks source link

Remove qtassistant and reduce size of mantiddocs package #37248

Open peterfpeterson opened 4 months ago

peterfpeterson commented 4 months ago

The current mantid help system uses qtassistant to show the help documentation. This method of including the docs is always in agreement with the code. However, there are some problems with this approach:

  1. The mantiddocs package is ~200MB. A large portion of this is because qtassistant wants the docs to be in a compressed archive and unpacked.
  2. qtassistant doesn't support javascript. javascript support would allow rendering equations in the application (rather than converting them to png via latex) and allow for sphinx plugins.
  3. The online docs are a separate build, with separate artifacts, from the offline docs

Describe the solution you'd like

Describe alternatives you've considered

qtwebview might be able to do it

Additional context

darshdinger commented 4 months ago

A Case for QtWebView

As per Pete's request, I will be do a brief overview of qtwebview to showcase its features, it's limitations, and give an example of a very basic implementation within another software package called SNAPRed.

Documentation Reference

Here is a link to the qtwebview doc page for Qt version 5.15.xx: https://doc.qt.io/qt-5/qtwebview-index.html

Qtwebview is also supported within Qt version 6.7.x and here is a link to that documentation page: https://doc.qt.io/qt-6/qtwebview-index.html

Prerequisites for Integration:

Addition of PyQtWebEngine within the conda environment:

- pyqtwebengine

Basic Overview of QtWebView:

QtWebView is a component within the Qt framework that enables the display of web content within QML applications. This viewer is lightweight and has no requirement of a complete web browser setup. QtWebView can be employed using either local built html files or web based URL addresses. Found at this ink is an overview of all the members available.

Two particularly notable methods include:

  1. runJavaScript(string script, variant callback)
  1. loadHtml(string html, url baseUrl)
Note: WebView does not support loading content through the Qt Resource System.

Basic Implementation within SNAPRed:

from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QWidget

from snapred.meta.Config import Config

class UserDocsButton(QWidget):
    def __init__(self, parent=None):
        super(UserDocsButton, self).__init__(parent)
        self.setStyleSheet("background-color: #F5E9E2;")
        self.initUI()

    def initUI(self):
        # Layout setup
        layout = QHBoxLayout(self)
        self.setLayout(layout)

        # Button to launch the web view
        self.button = QPushButton("User Documentation", self)
        self.button.setStyleSheet("background-color: #F5E9E2;")
        self.button.clicked.connect(self.launchWebView)
        layout.addWidget(self.button)

    def launchWebView(self):
        # Create and configure the web view
        self.webView = QWebEngineView()
        self.webView.setWindowTitle("Documentation")
        self.webView.resize(800, 600)

        # Point to the specific file on the filesystem
        url = QUrl.fromLocalFile(str(Config["docs.user.path"]))
        self.webView.setUrl(url)

        # Show the web view
        self.webView.show()

In the example above a widget is implemented within the PyQt framework to enable the users to access the documentation through the UI. This script defines a simple button and connects it to a method which launches an instance of QWebEngineView with a specified target pointing to a local system html file. Loading from a web based URL would be very similar and should be as simple as changing a single line.

Result:

image

Additional UI features for the web viewer can be added.

peterfpeterson commented 4 months ago

QtWebEngine needs to get imported before the QApplication is created. This will have to happen in the startup code for workbench. To see the error

from qtpy import QtWidgets, QtCore
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)

app = QtWidgets.QApplication([''])
from qtpy.QtWebEngineWidgets import QtWebChannel, QWebEngineView

reversing the order of the last two lines fixes the issue.

darshdinger commented 1 month ago

Here is a prototype for this implementation. Please look at the attached files. There is still some work to be done to find the solution between good rendering of the equations and retaining the layout of the doc pages.

Please take a look at these:

README.md requirements.txt

Script

import sys
import os
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
from http.server import SimpleHTTPRequestHandler, HTTPServer
import threading

class MyRequestInterceptor(QWebEngineUrlRequestInterceptor):
    def interceptRequest(self, info):
        url = info.requestUrl()
        if url.host() == "cdn.jsdelivr.net":
            info.setHttpHeader(b"Access-Control-Allow-Origin", b"*")

class HelpWindow(QWidget):
    def __init__(self, rootDir, parent=None):
        super().__init__(parent)
        self.setWindowTitle('Mantid Help Window')
        self.setGeometry(300, 100, 1200, 800)

        # Assuming the HTML files are directly in the rootDir
        self.convertDiffcalPath = "http://localhost:8000/ConvertDiffCal.html"
        self.anotherPagePath = "http://localhost:8000/EstimateResolutionDiffraction.html"

        layout = QVBoxLayout(self)

        self.webView = QWebEngineView()

        # Create and set the request interceptor
        interceptor = MyRequestInterceptor()
        self.webView.page().profile().setUrlRequestInterceptor(interceptor)

        layout.addWidget(self.webView)

        self.toggleButton = QPushButton('Go to another page')
        self.toggleButton.clicked.connect(self.togglePage)
        layout.addWidget(self.toggleButton)

        self.currentPage = 'convertDiffcal'
        self.loadPage(self.convertDiffcalPath)

    def loadPage(self, url):
        self.webView.setUrl(QUrl(url))

    def togglePage(self):
        if self.currentPage == 'convertDiffcal':
            self.loadPage(self.anotherPagePath)
            self.currentPage = 'anotherPage'
            self.toggleButton.setText('Go back to first page')
        else:
            self.loadPage(self.convertDiffcalPath)
            self.currentPage = 'convertDiffcal'
            self.toggleButton.setText('Go to another page')

def startLocalServer(rootDir):
    os.chdir(rootDir)
    handler = SimpleHTTPRequestHandler
    httpd = HTTPServer(('localhost', 8000), handler)
    httpd.serve_forever()

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python script.py /path/to/docs/html/")
        sys.exit(1)

    ROOT_DIR = sys.argv[1]

    # Start the local server in a separate thread
    serverThread = threading.Thread(target=startLocalServer, args=(ROOT_DIR,), daemon=True)
    serverThread.start()

    app = QApplication(sys.argv)

    # Launch the Help Window
    mainWin = HelpWindow(ROOT_DIR)
    mainWin.show()
    sys.exit(app.exec_())
peterfpeterson commented 4 weeks ago

boost::python 1.84 docs for calling python functions and methods