25
Provide tools to access the FreeCAD documentation.
27
The main usage is using the "show" function. It can retrieve an URL,
28
a local file (markdown or html), or find a page automatically from
29
the settings set under Preferences->General->Help.
31
It doesn't matter what you give, the system will recognize if the contents are
32
HTML or Markdown and render it appropriately.
37
Help.show("Draft Line")
38
Help.show("Draft_Line") # works with spaces or underscores
39
Help.show("https://wiki.freecadweb.org/Draft_Line")
40
Help.show("https://gitlab.com/freecad/FreeCAD-documentation/-/raw/main/wiki/Draft_Line.md")
41
Help.show("/home/myUser/.FreeCAD/Documentation/Draft_Line.md")
42
Help.show("http://myserver.com/myfolder/Draft_Line.html")
44
Preferences keys (in "User parameter:BaseApp/Preferences/Mod/Help"):
46
optionBrowser/optionTab/optionDialog (bool): Where to open the help dialog
47
optionOnline/optionOffline (bool): where to fetch the documentation from
48
URL (string): online location
49
Location (string): offline location
50
Suffix (string): a suffix to add to the URL, ex: /fr
51
StyleSheet (string): optional CSS stylesheet to style the output
58
translate = FreeCAD.Qt.translate
59
QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
62
WIKI_URL = "https://wiki.freecad.org"
63
MD_RAW_URL = "https://raw.githubusercontent.com/FreeCAD/FreeCAD-documentation/main/wiki"
64
MD_RENDERED_URL = "https://github.com/FreeCAD/FreeCAD-documentation/blob/main/wiki"
65
MD_TRANSLATIONS_FOLDER = "translations"
68
"Contents for this page could not be retrieved. Please check settings under menu Edit -> Preferences -> General -> Help",
72
"Help files location could not be determined. Please check settings under menu Edit -> Preferences -> General -> Help",
76
"PySide QtWebEngineWidgets module is not available. Help rendering is done with the Web module",
78
CONVERTTXT = translate(
80
"There is no markdown renderer installed on your system, so this help page is rendered as is. Please install the markdown or pandoc python modules to improve the rendering of this page.",
82
PREFS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Help")
83
ICON = ":/icons/help-browser.svg"
86
def show(page, view=None, conv=None):
88
show(page,view=None, conv=None):
89
Opens a help viewer and shows the given help page.
90
The given help page can be a URL pointing to a markdown or HTML file,
91
a name page / command name, or a file path pointing to a markdown
92
or HTML file. If view is given (an instance of openBrowserHTML.HelpPage or
93
any other object with a 'setHtml()' method), the page will be
94
rendered there, otherwise a new tab/widget will be created according to
95
preferences settings. If conv is given (markdown, pandoc, github, builtin or
96
none), the corresponding markdown conversion method is used. Otherwise, the
97
module will use the best available.
98
In non-GUI mode, this function simply outputs the markdown or HTML text.
101
page = underscore_page(page)
102
location = get_location(page)
103
FreeCAD.Console.PrintLog("Help: opening " + location + "\n")
105
FreeCAD.Console.PrintError(LOCTXT + "\n")
107
md = get_contents(location)
108
html = convert(md, conv)
109
baseurl = get_uri(location)
110
pagename = os.path.basename(page.replace("_", " ").replace(".md", ""))
111
title = translate("Help", "Help") + ": " + pagename
113
if PREFS.GetBool("optionBrowser", False):
114
show_browser(location)
115
elif PREFS.GetBool("optionDialog", False):
116
show_dialog(html, baseurl, title, view)
118
show_tab(html, baseurl, title, view)
124
def underscore_page(page):
125
"""change spaces by underscores in the given page name"""
128
page = page.split("/")
129
page[-1] = page[-1].replace(" ", "_")
130
page = "/".join(page)
132
page.replace(" ", "_")
136
def get_uri(location):
137
"""returns a valid URI from a disk or network location"""
139
baseurl = os.path.dirname(location) + "/"
140
if baseurl.startswith("/"):
141
baseurl = "file://" + baseurl
142
if baseurl[0].isupper() and (baseurl[1] == ":"):
143
baseurl = baseurl.replace("\\", "/")
144
baseurl = "file:///" + baseurl
148
def get_location(page):
149
"""retrieves the location (online or offline) of a given page"""
152
if page.startswith("http"):
154
if page.startswith("file://"):
157
if os.path.exists(page):
159
page = page.replace(".md", "")
160
page = page.replace(" ", "_")
161
page = page.replace("wiki/", "")
162
page = page.split("#")[0]
163
suffix = PREFS.GetString("Suffix", "")
165
if not suffix.startswith("/"):
166
suffix = "/" + suffix
167
if PREFS.GetBool("optionWiki", True):
168
location = WIKI_URL + "/" + page + suffix
169
elif PREFS.GetBool("optionMarkdown", False):
170
if PREFS.GetBool("optionBrowser", False):
171
location = MD_RENDERED_URL
173
location = MD_RAW_URL
175
location += "/" + MD_TRANSLATIONS_FOLDER + suffix
176
location += "/" + page + ".md"
177
elif PREFS.GetBool("optionGithub", False):
178
location = MD_RENDERED_URL
180
location += "/" + MD_TRANSLATIONS_FOLDER + suffix
181
location += "/" + page + ".md"
182
elif PREFS.GetBool("optionCustom", False):
183
location = PREFS.GetString("Location", "")
185
location = os.path.join(
186
FreeCAD.getUserAppDataDir(),
188
"offline-documentation",
189
"FreeCAD-documentation-main",
192
location = os.path.join(location, page + ".md")
196
def show_browser(url):
197
"""opens the desktop browser with the given URL"""
199
from PySide import QtCore, QtGui
202
ret = QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
207
webbrowser.open_new(url)
211
webbrowser.open_new(url)
214
def show_dialog(html, baseurl, title, view=None):
215
"""opens a dock dialog with the given html"""
217
from PySide import QtCore
219
if get_qtwebwidgets(html, baseurl, title):
221
view.setHtml(html, baseUrl=QtCore.QUrl(baseurl))
222
view.parent().parent().setWindowTitle(title)
224
openBrowserHTML(html, baseurl, title, ICON, dialog=True)
227
def show_tab(html, baseurl, title, view=None):
228
"""opens a MDI tab with the given html"""
230
from PySide import QtCore
232
if get_qtwebwidgets(html, baseurl, title):
234
view.setHtml(html, baseUrl=QtCore.QUrl(baseurl))
235
view.parent().parent().setWindowTitle(title)
242
WebGui.openBrowserHTML(html, baseurl, title, ICON)
245
def get_qtwebwidgets(html, baseurl, title):
246
"""opens a web module view if qtwebwidgets module is not available, and returns False"""
249
from PySide import QtGui, QtWebEngineWidgets
251
FreeCAD.Console.PrintLog(LOGTXT + "\n")
254
WebGui.openBrowserHTML(html, baseurl, title, ICON)
260
def get_contents(location):
261
"""retrieves text contents of a given page"""
265
if location.startswith("http"):
266
import urllib.request
269
r = urllib.request.urlopen(location)
272
contents = r.read().decode("utf8")
275
if os.path.exists(location):
276
with open(location, mode="r", encoding="utf8") as f:
282
def convert(content, force=None):
283
"""converts the given markdown code to html. Force can be None (automatic)
284
or markdown, pandoc, github or raw/builtin"""
288
def convert_markdown(m):
291
from markdown.extensions import codehilite
293
return markdown.markdown(m, extensions=["codehilite"])
297
def convert_pandoc(m):
301
return pypandoc.convert_text(m, "html", format="md")
305
def convert_github(m):
308
import urllib.request
310
data = {"text": m, "mode": "markdown"}
311
bdata = json.dumps(data).encode("utf-8")
313
urllib.request.urlopen("https://api.github.com/markdown", data=bdata)
325
f = re.DOTALL | re.MULTILINE
326
m = re.sub(r"^##### (.*?)\n", r"<h5>\1</h5>\n", m, flags=f)
327
m = re.sub(r"^#### (.*?)\n", r"<h4>\1</h4>\n", m, flags=f)
328
m = re.sub(r"^### (.*?)\n", r"<h3>\1</h3>\n", m, flags=f)
329
m = re.sub(r"^## (.*?)\n", r"<h2>\1</h2>\n", m, flags=f)
330
m = re.sub(r"^# (.*?)\n", r"<h1>\1</h1>\n", m, flags=f)
331
m = re.sub(r"!\[(.*?)\]\((.*?)\)", r'<img alt="\1" src="\2">', m, flags=f)
332
m = re.sub(r"\[(.*?)\]\((.*?)\)", r'<a href="\2">\1</a>', m, flags=f)
333
m = re.sub(r"\*\*(.*?)\*\*", r"<b>\1</b>", m)
334
m = re.sub(r"\*(.*?)\*", r"<i>\1</i>", m)
335
m = re.sub(r"\n\n", r"<br/>", m, flags=f)
336
m += "\n<br/><hr/><small>" + CONVERTTXT + "</small>"
339
if "<html" in content:
343
if force == "markdown":
344
html = convert_markdown(content)
345
elif force == "pandoc":
346
html = convert_pandoc(content)
347
elif force == "github":
348
html = convert_github(content)
349
elif force in ["raw", "builtin"]:
350
html = convert_raw(content)
351
elif force == "none":
355
html = convert_pandoc(content)
357
html = convert_markdown(content)
359
html = convert_raw(content)
360
if not "<html" in html:
362
'<html>\n<head>\n<meta charset="utf-8"/>\n</head>\n<body>\n\n'
368
cssfile = PREFS.GetString("StyleSheet", "")
370
cssfile = os.path.join(os.path.dirname(__file__), "default.css")
373
cssfile = urllib.parse.urljoin("file:", urllib.request.pathname2url(cssfile))
374
css = '<link rel="stylesheet" type="text/css" href="' + cssfile + '"/>'
376
if os.path.exists(cssfile):
377
with open(cssfile) as cf:
380
css = "<style>\n" + css + "\n</style>"
382
print("Debug: Help: Unable to open css file:", cssfile)
384
html = html.replace("</head>", css + "\n</head>")
388
def add_preferences_page():
389
"""adds the Help preferences page to the UI"""
393
page = os.path.join(os.path.dirname(__file__), "dlgPreferencesHelp.ui")
394
FreeCADGui.addPreferencePage(page, QT_TRANSLATE_NOOP("QObject", "General"))
397
def add_language_path():
398
"""registers the Help translations to FreeCAD"""
403
FreeCADGui.addLanguagePath(":/translations")
406
def openBrowserHTML(html, baseurl, title, icon, dialog=False):
407
"""creates a browser view and adds it as a FreeCAD MDI tab or dockable dialog"""
410
from PySide import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
413
def getDockArea(area):
415
return QtCore.Qt.LeftDockWidgetArea
417
return QtCore.Qt.TopDockWidgetArea
419
return QtCore.Qt.BottomDockWidgetArea
421
return QtCore.Qt.RightDockWidgetArea
424
def onDockLocationChanged(area):
425
PREFS.SetInt("dockWidgetArea", int(area))
426
mw = FreeCADGui.getMainWindow()
427
dock = mw.findChild(QtWidgets.QDockWidget, "HelpWidget")
429
PREFS.SetBool("dockWidgetFloat", dock.isFloating())
430
PREFS.SetInt("dockWidgetWidth", dock.width())
431
PREFS.SetInt("dockWidgetHeight", dock.height())
434
class HelpPage(QtWebEngineWidgets.QWebEnginePage):
435
def acceptNavigationRequest(self, url, _type, isMainFrame):
436
if _type == QtWebEngineWidgets.QWebEnginePage.NavigationTypeLinkClicked:
437
show(url.toString(), view=self)
438
return super().acceptNavigationRequest(url, _type, isMainFrame)
440
mw = FreeCADGui.getMainWindow()
441
view = QtWebEngineWidgets.QWebEngineView()
442
page = HelpPage(None, view)
443
page.setHtml(html, baseUrl=QtCore.QUrl(baseurl))
447
area = PREFS.GetInt("dockWidgetArea", 2)
448
floating = PREFS.GetBool("dockWidgetFloat", True)
449
height = PREFS.GetBool("dockWidgetWidth", 200)
450
width = PREFS.GetBool("dockWidgetHeight", 300)
451
dock = mw.findChild(QtWidgets.QDockWidget, "HelpWidget")
453
dock = QtWidgets.QDockWidget()
454
dock.setObjectName("HelpWidget")
455
mw.addDockWidget(getDockArea(area), dock)
456
dock.setFloating(floating)
457
dock.setGeometry(dock.x(), dock.y(), width, height)
458
dock.dockLocationChanged.connect(onDockLocationChanged)
460
dock.setWindowTitle(title)
461
dock.setWindowIcon(QtGui.QIcon(icon))
464
mdi = mw.findChild(QtWidgets.QMdiArea)
465
sw = mdi.addSubWindow(view)
466
sw.setWindowTitle(title)
467
sw.setWindowIcon(QtGui.QIcon(icon))
469
mdi.setActiveSubWindow(sw)