Inline documentation options

For SlicerMorph, we maintain a tutorials page as a github repo:

I want to have a Tutorials module within SlicerMorph so that people can navigate to this site easily (or be aware of its existence). I can of course simply point this out as a URL in a basic module interface. But given that we have nice README.MD file with links to the tutorials, I am wondering if there is a way to import this dynamically? I.e., I do not want to replicate the contents of the README.MD as part of the module help page (too much duplicated work).

I guess I am looking into something like making a snapshot of this README during module build and rendered with clickable links…

I am still fuzzy on details, so can use suggestions.

Well you could bundle the contents of the tutorials (either just the readme or the full repo contents) when the extension is built or you could assume people will have internet when they use it and fetch dynamically.

Do you want to show the tutorials in a web view that can interact somehow with Slicer or just have them in an independent system browser?

This is sort of what I want to, when I choose SlicerMorph->Tutorials from the module panel, this page shows up like this:

Then clicking on a link, opens the tutorial in the system registered web browser.

I am not interested in offline availability or want to use the internal browser.

If you are using the external browser I think it would be much easier just to open the readme page in the external browser. So you would just open that from a button in Slicer. Otherwise you’l be messing with getting the rendered markdown into a Slicer widget and handling links, which can be done (most easily with a webwidget) but it doesn’t look like it buys you much.

That means you need

true, but that means you know the URL before hand. Point of this is increasing the discoverability of the content.

For example. I can have convert the README.MD to html via tool, and dump it in the self.parent.helpText = field, which gives me this. But I want this to be in the module panel, and not the help field which is almost always overlooked.

yes, that’s what I was would be the easiest, just to use an html widget in Slicer (could be a full web widget or just a simpler html render) but you need to integrate a markdown to html converter somewhere, either in slicer or in your build process or fetch the html from github when you open the module.

Supposedly QT support markdown? QTextEdit Class | Qt Widgets 5.15.18

I didn’t know about that and it might work, but remember there are ‘flavors’ of markdown and that could bite you at some point (looks like Qt supports a basic one). In any case you can get the contents either at build time or run time.

True. But I think we use a very minimal and fairly standard subset on that Readme.
Mostly heading levels #, ##, ###, emphasis (**), and link.

I used Bing Chat to build the module. Seems to work, but can’t quite get it to use the full height of the module panel

import os
import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
import logging
import mistune
import requests
import webbrowser

class SlicerMorphTutorials(ScriptedLoadableModule):

    def __init__(self, parent):
        ScriptedLoadableModule.__init__(self, parent)
        self.parent.title = "SlicerMorph Tutorials"
        self.parent.categories = ["Examples"]
        self.parent.dependencies = []
        self.parent.contributors = ["Your Name (Your Institution)"]
        self.parent.helpText = """This is an example of a scripted module that displays Markdown text in a QTextBrowser."""
        self.parent.acknowledgementText = """This file was originally developed by Your Name, Your Institution."""

class SlicerMorphTutorialsWidget(ScriptedLoadableModuleWidget):

    def setup(self):
        ScriptedLoadableModuleWidget.setup(self)

        # URL of the Markdown file
        markdown_url = "https://raw.githubusercontent.com/SlicerMorph/Tutorials/main/README.md"

        # Fetch the Markdown content
        response = requests.get(markdown_url)
        markdown_text = response.text

        # Create a Markdown renderer
        renderer = mistune.create_markdown()

        # Convert Markdown to HTML
        html_content = renderer(markdown_text)

        # Add some basic styling for better display
        styled_html_content = f"""
        <html>
        <head>
        <style>
        body {{ font-family: Arial, sans-serif; }}
        h1 {{ color: #333; }}
        ul {{ list-style-type: disc; padding-left: 20px; }}
        li {{ margin: 5px 0; }}
        </style>
        </head>
        <body>
        {html_content}
        </body>
        </html>
        """

        # Create a QTextBrowser widget
        self.textWidget = qt.QTextBrowser()
        self.textWidget.setReadOnly(True)
        self.textWidget.setOpenExternalLinks(True)  # Ensure external links open in the default web browser

        # Set the styled HTML content in the QTextBrowser widget
        self.textWidget.setHtml(styled_html_content)

        # Create a vertical layout for the widget
        layout = qt.QVBoxLayout()

        # Add the QTextBrowser widget with full stretch factor
        layout.addWidget(self.textWidget)

        # Set the layout for the module
        self.layout.addLayout(layout)

    def cleanup(self):
        pass

class SlicerMorphTutorialsLogic(ScriptedLoadableModuleLogic):
    pass

class SlicerMorphTutorialsTest(ScriptedLoadableModuleTest):
    def runTest(self):
        self.setUp()
        self.test_SlicerMorphTutorials1()

    def test_SlicerMorphTutorials1(self):
        self.delayDisplay("Starting the test")
        self.delayDisplay('Test passed!')


If you want to package the exact version of the tutorial the module was built with, it may be worthwhile to package the sphinx HTML documentation directly with the module during the build / install step in the CI. (it would also be useful for offline usage).

For the height of the widget, maybe changing the size policy to MinimumExpanding could help.

Python stdlib has a module called “webbrowser”. You can do this:

import webbrowser
webbrowser.open("https://slicer.org")

and it opens a web page in your external browser. This works from within Slicer (for me on Linux, but I expect elsewhere too), so you could trigger that from a button in your module. The users don’t need to know the URL in advance.

I thought this was what @pieper meant in the message you replied to.