diff --git a/.gitignore b/.gitignore index 36320927..1cc84316 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ # Documentation /docs/build/ +novelWriter.qch novelWriter.qhc # Python Temp diff --git a/docs/source/started.rst b/docs/source/started.rst index 47269fa0..6cb2f3e3 100644 --- a/docs/source/started.rst +++ b/docs/source/started.rst @@ -51,7 +51,7 @@ the following command: .. code-block:: console - python -m pip install -r requirements.txt + pip install -r requirements.txt On some operating systems you need to use ``python3`` instead of ``python``. @@ -78,6 +78,31 @@ The spell checking extension is optional, but recommended: The optional spell check library must be at least 3.0.0 to work with Windows. On Linux, 2.0.0 also works fine. +.. _a_started_depend_docs: + +Building Documentation +---------------------- + +If you installed novelWriter from a package, the documentation should be included. If you're running +novelWriter from the source code, a local copy of this documentation can be generated. It requires +the following Python packages on Debian and Ubuntu. + +* ``python3-sphinx`` +* ``python3-sphinxcontrib.qthelp`` + +Or from PyPi: + +.. code-block:: console + + pip install sphinx sphinxcontrib-qthelp + +To build the help packages from the documentation source, run + +.. code-block:: console + + ./setup.py qthelp + +from the root source folder. .. _a_started_running: diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 43b95385..c21629b3 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -28,7 +28,9 @@ import logging import nw -from PyQt5.QtCore import QUrl +from os import path + +from PyQt5.QtCore import QUrl, QProcess from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox @@ -47,6 +49,10 @@ class GuiMainMenu(QMenuBar): self.theParent = theParent self.theProject = theParent.theProject + self.assistProc = None + self.helpPath = path.join(self.mainConf.assetPath, "help", "novelWriter.qhc") + self.hasHelp = path.isfile(self.helpPath) + self._buildProjectMenu() self._buildDocumentMenu() self._buildEditMenu() @@ -67,6 +73,10 @@ class GuiMainMenu(QMenuBar): return + ## + # Methods + ## + def setAvailableRoot(self): for itemClass in nwItemClass: if itemClass == nwItemClass.NO_CLASS: continue @@ -76,6 +86,25 @@ class GuiMainMenu(QMenuBar): ) return + def closeHelp(self): + """Close the process used for the Qt Assistant, if it is open. + """ + if self.assistProc is None: + return + + if self.assistProc.state() == QProcess.Starting: + if self.assistProc.waitForStarted(10000): + self.assistProc.terminate() + else: + self.assistProc.kill() + + elif self.assistProc.state() == QProcess.Running: + self.assistProc.terminate() + if not self.assistProc.waitForFinished(10000): + self.assistProc.kill() + + return + ## # Update Menu on Settings Changed ## @@ -134,10 +163,21 @@ class GuiMainMenu(QMenuBar): msgBox.aboutQt(self.theParent,"About Qt") return True - def _openHelp(self): - """Open the documentation URL in the system's default browser. + def _openAssistant(self): + """Open the documentation in Qt Assistant. """ - QDesktopServices.openUrl(QUrl(nw.__docurl__)) + self.assistProc = QProcess(self) + self.assistProc.start("assistant", [ + "-collectionFile", self.helpPath, "-enableRemoteControl" + ]) + if not self.assistProc.waitForStarted(20000): + return False + return True + + def _openWebsite(self, theUrl): + """Open an URL in the system's default browser. + """ + QDesktopServices.openUrl(QUrl(theUrl)) return True def _openIssue(self): @@ -818,17 +858,27 @@ class GuiMainMenu(QMenuBar): # Help > Separator self.helpMenu.addSeparator() - # Document > Preview - self.aHelp = QAction("Online Documentation", self) - self.aHelp.setStatusTip("View online documentation") + # Document > Documentation + self.aHelp = QAction("Documentation", self) + if self.hasHelp: + self.aHelp.setStatusTip("View local documentation with Qt Assistant") + self.aHelp.triggered.connect(self._openAssistant) + else: + self.aHelp.setStatusTip("View online documentation") + self.aHelp.triggered.connect(lambda: self._openWebsite(nw.__docurl__)) self.aHelp.setShortcut("F1") - self.aHelp.triggered.connect(self._openHelp) self.helpMenu.addAction(self.aHelp) + # Document > Go to Website + self.aWebsite = QAction("Open the novelWriter Website", self) + self.aWebsite.setStatusTip("View the main website") + self.aWebsite.triggered.connect(lambda: self._openWebsite(nw.__url__)) + self.helpMenu.addAction(self.aWebsite) + # Document > Report Issue self.aIssue = QAction("Report an Issue", self) - self.aIssue.setStatusTip("View online documentation") - self.aIssue.triggered.connect(self._openIssue) + self.aIssue.setStatusTip("Report a bug or issue on GitHub") + self.aIssue.triggered.connect(lambda: self._openWebsite(nw.__issuesurl__)) self.helpMenu.addAction(self.aIssue) return diff --git a/nw/guimain.py b/nw/guimain.py index 12655f20..2571d819 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -892,6 +892,7 @@ class GuiMain(QMainWindow): self.mainConf.saveConfig() self.reportConfErr() + self.mainMenu.closeHelp() qApp.quit() diff --git a/setup.py b/setup.py index e44a73b1..818af1a2 100755 --- a/setup.py +++ b/setup.py @@ -17,10 +17,14 @@ if "qthelp" in sys.argv: sys.argv.remove("qthelp") if buildDocs: - inFile = os.path.join("docs", "build", "qthelp", "novelWriter.qhcp") - outFile = os.path.join("docs", "build", "qthelp", "novelWriter.qhc") - helpDir = os.path.join("nw", "assets", "help") - dstFile = os.path.join(helpDir, "novelWriter.qhc") + + buildDir = os.path.join("docs", "build", "qthelp") + helpDir = os.path.join("nw", "assets", "help") + + inFile = "novelWriter.qhcp" + outFile = "novelWriter.qhc" + datFile = "novelWriter.qch" + print("") print("Building Documentation") print("======================") @@ -35,7 +39,7 @@ if buildDocs: buildFail = True try: - subprocess.call(["qhelpgenerator", inFile]) + subprocess.call(["qhelpgenerator", os.path.join(buildDir, inFile)]) except Exception as e: print("Failed with error:") print(str(e)) @@ -50,9 +54,12 @@ if buildDocs: buildFail = True try: - if os.path.isfile(dstFile): - os.unlink(dstFile) - os.rename(outFile, dstFile) + if os.path.isfile(os.path.join(helpDir, outFile)): + os.unlink(os.path.join(helpDir, outFile)) + if os.path.isfile(os.path.join(helpDir, datFile)): + os.unlink(os.path.join(helpDir, datFile)) + os.rename(os.path.join(buildDir, outFile), os.path.join(helpDir, outFile)) + os.rename(os.path.join(buildDir, datFile), os.path.join(helpDir, datFile)) except Exception as e: print("Failed with error:") print(str(e))