From 1d9cd3ee49cfee756476610a8ddd0c82786829bb Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 17 Aug 2020 22:39:38 +0200 Subject: [PATCH 1/7] Add a workaround for getting tabs through the html parsing and setHtml function in QTextBrowser --- nw/core/tohtml.py | 2 +- nw/gui/docviewer.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 74c5a676..0c6199c2 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -77,7 +77,7 @@ class ToHtml(Tokenizer): self.doKeywords = True self.doComments = doComments self.doSynopsis = doSynopsis - self.repDict["\t"] = " "*8 + self.repDict["\t"] = r"!!tab!!" # The viewer replaces these self._buildRegEx() return diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 13707ae9..3c7c9795 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -167,6 +167,18 @@ class GuiDocViewer(QTextBrowser): # Make sure the main GUI knows we changed the content self.theParent.viewMeta.refreshReferences(tHandle) + # Loop through the text and put back in the tabs. Tabs are removed by + # the setHtml function, so the ToHtml class puts in a placeholder. + while self.find("!!tab!!"): + theCursor = self.textCursor() + theCursor.insertText("\t") + + # Refresh the tab stops + if self.mainConf.verQtValue >= 51000: + self.setTabStopDistance(self.mainConf.getTabWidth()) + else: + self.setTabStopWidth(self.mainConf.getTabWidth()) + return True def reloadText(self): From 10936b69102f6a62528e1adc03b3f4fa633d3f3b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 17 Aug 2020 22:40:04 +0200 Subject: [PATCH 2/7] Implement the same tab stop code in the editor as for the viewer --- nw/gui/doceditor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 737758bf..4cecfc7a 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -206,8 +206,6 @@ class GuiDocEditor(QTextEdit): # Also set the document text options for the document text flow theOpt = QTextOption() - if self.mainConf.verQtValue >= 51000: - theOpt.setTabStopDistance(self.mainConf.getTabWidth()) if self.mainConf.doJustify: theOpt.setAlignment(Qt.AlignJustify) if self.mainConf.showTabsNSpaces: @@ -288,6 +286,12 @@ class GuiDocEditor(QTextEdit): self.hLight.spellCheck = spTemp qApp.restoreOverrideCursor() + # Refresh the tab stops + if self.mainConf.verQtValue >= 51000: + self.setTabStopDistance(self.mainConf.getTabWidth()) + else: + self.setTabStopWidth(self.mainConf.getTabWidth()) + return True def replaceText(self, theText): From 2f177c50fcdf107bb72eaa98e3d2988af78fd3ee Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Aug 2020 00:21:25 +0200 Subject: [PATCH 3/7] Improved the tab handling and added the same fix to the build tool --- nw/core/tohtml.py | 4 ++-- nw/gui/build.py | 27 +++++++++++++++++++++++++-- nw/gui/docviewer.py | 34 ++++++++++++++++++++-------------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 0c6199c2..bfb60984 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -48,7 +48,7 @@ class ToHtml(Tokenizer): "<" : "<", ">" : ">", "&" : "&", - "\t" : " "*2, + # "\t" : "!!tab!!", # " "*2, nwUnicode.U_ENDASH : nwUnicode.H_ENDASH, nwUnicode.U_EMDASH : nwUnicode.H_EMDASH, nwUnicode.U_HELLIP : nwUnicode.H_HELLIP, @@ -77,7 +77,7 @@ class ToHtml(Tokenizer): self.doKeywords = True self.doComments = doComments self.doSynopsis = doSynopsis - self.repDict["\t"] = r"!!tab!!" # The viewer replaces these + # self.repDict["\t"] = r"!!tab!!" # The viewer replaces these self._buildRegEx() return diff --git a/nw/gui/build.py b/nw/gui/build.py index 81af3fa4..c13a3911 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -551,7 +551,6 @@ class GuiBuildNovel(QDialog): isNone |= theItem.itemClass == nwItemClass.NO_CLASS isNone |= theItem.itemClass == nwItemClass.TRASH isNone |= theItem.parHandle == self.theProject.projTree.trashRoot() - isNone |= theItem.parHandle == self.theProject.projTree.archiveRoot() isNone |= theItem.parHandle is None isNote = theItem.itemLayout == nwItemLayout.NOTE isNovel = not isNone and not isNote @@ -563,6 +562,10 @@ class GuiBuildNovel(QDialog): if isNovel and not novelFiles: return False + rootItem = self.theProject.projTree.getRootItem(theItem.itemHandle) + if rootItem.itemClass == nwItemClass.ARCHIVE: + return False + return True def _saveDocument(self, theFormat): @@ -1014,13 +1017,33 @@ class GuiBuildNovelDocView(QTextBrowser): theText = "".join(theText) self.buildTime = timeStamp + sPos = self.verticalScrollBar().value() - theText = theText.replace(" ", " "*4) + # Refresh the tab stops + if self.mainConf.verQtValue >= 51000: + self.setTabStopDistance(self.mainConf.getTabWidth()) + else: + self.setTabStopWidth(self.mainConf.getTabWidth()) + + theText = theText.replace("\t", "!!tab!!") theText = theText.replace("", "") theText = theText.replace("", "") self.setHtml(theText) + + while self.find("!!tab!!"): + theCursor = self.textCursor() + theCursor.insertText("\t") + + self.verticalScrollBar().setValue(sPos) self._updateBuildAge() + # This forces a repaint of the text, It's a hack to fix an occational + # issue where the find/replace above interfers with the rendering and + # leaves parts of the document blank. + textWidth = self.qDocument.textWidth() + self.qDocument.setTextWidth(textWidth - 5) + self.qDocument.setTextWidth(textWidth) + return def setStyleSheet(self, theStyles=[]): diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 3c7c9795..0f7e81cd 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -156,7 +156,20 @@ class GuiDocViewer(QTextBrowser): self.setText("An error occurred while generating the preview.") return False - self.setHtml(aDoc.theResult) + # Refresh the tab stops + if self.mainConf.verQtValue >= 51000: + self.setTabStopDistance(self.mainConf.getTabWidth()) + else: + self.setTabStopWidth(self.mainConf.getTabWidth()) + + self.setHtml(aDoc.theResult.replace("\t", "!!tab!!")) + + # Loop through the text and put back in the tabs. Tabs are removed by + # the setHtml function, so the ToHtml class puts in a placeholder. + while self.find("!!tab!!"): + theCursor = self.textCursor() + theCursor.insertText("\t") + if self.theHandle == tHandle: self.verticalScrollBar().setValue(sPos) self.theHandle = tHandle @@ -167,17 +180,12 @@ class GuiDocViewer(QTextBrowser): # Make sure the main GUI knows we changed the content self.theParent.viewMeta.refreshReferences(tHandle) - # Loop through the text and put back in the tabs. Tabs are removed by - # the setHtml function, so the ToHtml class puts in a placeholder. - while self.find("!!tab!!"): - theCursor = self.textCursor() - theCursor.insertText("\t") - - # Refresh the tab stops - if self.mainConf.verQtValue >= 51000: - self.setTabStopDistance(self.mainConf.getTabWidth()) - else: - self.setTabStopWidth(self.mainConf.getTabWidth()) + # This forces a repaint of the text, It's a hack to fix an occational + # issue where the find/replace above interfers with the rendering and + # leaves parts of the document blank. + textWidth = self.qDocument.textWidth() + self.qDocument.setTextWidth(textWidth - 5) + self.qDocument.setTextWidth(textWidth) return True @@ -369,8 +377,6 @@ class GuiDocViewer(QTextBrowser): "}}\n" ".comment {{" " color: rgb({cColR},{cColG},{cColB});" - " margin-left: 1em;" - " margin-right: 1em;" "}}\n" ".synopsis {{" " color: rgb({mColR},{mColG},{mColB});" From a9ac3a2aa1cd6733d5ca0b6e7d5f4d8a8cedc935 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Aug 2020 00:29:18 +0200 Subject: [PATCH 4/7] Removed commented out code --- nw/core/tohtml.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index bfb60984..dadf55bd 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -45,10 +45,9 @@ class ToHtml(Tokenizer): self.cssStyles = True self.repDict = { - "<" : "<", - ">" : ">", - "&" : "&", - # "\t" : "!!tab!!", # " "*2, + "<" : "<", + ">" : ">", + "&" : "&", nwUnicode.U_ENDASH : nwUnicode.H_ENDASH, nwUnicode.U_EMDASH : nwUnicode.H_EMDASH, nwUnicode.U_HELLIP : nwUnicode.H_HELLIP, @@ -77,7 +76,6 @@ class ToHtml(Tokenizer): self.doKeywords = True self.doComments = doComments self.doSynopsis = doSynopsis - # self.repDict["\t"] = r"!!tab!!" # The viewer replaces these self._buildRegEx() return From b80ff750fbcde2009557b2fc246ddcdd9c8fca2d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Aug 2020 00:33:15 +0200 Subject: [PATCH 5/7] A bit less aggressive version off the redraw text hack --- nw/gui/build.py | 4 +--- nw/gui/docviewer.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/nw/gui/build.py b/nw/gui/build.py index c13a3911..0370e23c 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -1040,9 +1040,7 @@ class GuiBuildNovelDocView(QTextBrowser): # This forces a repaint of the text, It's a hack to fix an occational # issue where the find/replace above interfers with the rendering and # leaves parts of the document blank. - textWidth = self.qDocument.textWidth() - self.qDocument.setTextWidth(textWidth - 5) - self.qDocument.setTextWidth(textWidth) + self.qDocument.setTextWidth(self.qDocument.textWidth()) return diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 0f7e81cd..63a41e9b 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -183,9 +183,7 @@ class GuiDocViewer(QTextBrowser): # This forces a repaint of the text, It's a hack to fix an occational # issue where the find/replace above interfers with the rendering and # leaves parts of the document blank. - textWidth = self.qDocument.textWidth() - self.qDocument.setTextWidth(textWidth - 5) - self.qDocument.setTextWidth(textWidth) + self.qDocument.setTextWidth(self.qDocument.textWidth()) return True From 4a20a0eb017493b862be95cdebb2a701fb0f1971 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Aug 2020 08:50:08 +0200 Subject: [PATCH 6/7] Use markContentsDirty on the QTextDocument instead of the textWidth hack --- nw/gui/build.py | 7 +++---- nw/gui/docviewer.py | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/nw/gui/build.py b/nw/gui/build.py index 0370e23c..b61dfd0f 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -1037,10 +1037,9 @@ class GuiBuildNovelDocView(QTextBrowser): self.verticalScrollBar().setValue(sPos) self._updateBuildAge() - # This forces a repaint of the text, It's a hack to fix an occational - # issue where the find/replace above interfers with the rendering and - # leaves parts of the document blank. - self.qDocument.setTextWidth(self.qDocument.textWidth()) + # Since we change the content while it may still be rendering, we mark + # the document dirty again to make sure it's re-rendered properly. + self.qDocument.markContentsDirty(0, self.qDocument.characterCount()) return diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 63a41e9b..b613ac2f 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -180,10 +180,9 @@ class GuiDocViewer(QTextBrowser): # Make sure the main GUI knows we changed the content self.theParent.viewMeta.refreshReferences(tHandle) - # This forces a repaint of the text, It's a hack to fix an occational - # issue where the find/replace above interfers with the rendering and - # leaves parts of the document blank. - self.qDocument.setTextWidth(self.qDocument.textWidth()) + # Since we change the content while it may still be rendering, we mark + # the document dirty again to make sure it's re-rendered properly. + self.qDocument.markContentsDirty(0, self.qDocument.characterCount()) return True From 95dd867d47e661f44b9c58e6a5c02f22d8dc524e Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Aug 2020 20:11:57 +0200 Subject: [PATCH 7/7] Bring the readme file up to date --- README.md | 33 ++++++++++++++++++--------------- nw/gui/docviewer.py | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a56418a3..3c60e951 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ -novelWriter is a markdown-like text editor designed for writing novels and larger projects of many -smaller plain text documents. It uses its own flavour of markdown that supports a meta data syntax +novelWriter is a Markdown-like text editor designed for writing novels and larger projects of many +smaller plain text documents. It uses its own flavour of Markdown that supports a meta data syntax for comments, synopsis and cross-referencing between files. It's designed to be a simple text editor -which allows for easy organisation of text files and notes, built on plain text files for +that allows for easy organisation of text files and notes, built on plain text files for robustness. The plain text storage is suitable for version control software, and also well suited for file @@ -29,7 +29,7 @@ The contributing guide is available in [CONTRIBUTING](CONTRIBUTING.md). The default branch on this repository switched to `main` on 6. August 2020. If you are running novelWriter from a git clone, you need to clone the repository again. -Alternatively, you can run the following to get back on the main branch: +Alternatively, you can run the following to get back on the new default branch: ```bash git remote update @@ -48,8 +48,8 @@ novelWriter is in a _beta_ state. Please report any issues you may encounter in tracker. You should be able to use novelWriter for real projects, but as with all software, please make -regular backups. There is a built in backup feature that can pack the entire project into a zip file -on close. Please check the documentation for further details. +regular backups of your data. There is a built in backup feature that can pack the entire project +into a zip file on close. Please check the documentation for further details. ## License @@ -80,7 +80,7 @@ novelWriter is _not_ a full-feature Markdown editor. It allows for a minimal set needed for writing text documents for novels. These are currently limited to: * Headings level 1 to 4 using the `#` syntax only. -* Emphasised, strong text. These are rendered as italicised and bold. +* Emphasised and strong text. These are rendered as italicised and bold. * Strikethrough text. * Hard line breaks using two or more spaces at the end of a line. @@ -98,21 +98,24 @@ In addition, novelWriter adds the following, which is otherwise not supported by version, non-breaking spaces are converted to normal spaces when saving the document. This is done by the Qt library. * Thin spaces are also supported, as well as non-breaking thin spaces. -* Tabs may be rendered, depending on export format. With Qt 5.10 or higher, the width of a tab in - pixels can be changed in Preferences. +* Tabs can be used in the text, and should be properly aligned. The width of a tab in pixels can be + changed in Preferences. Note that tabs are exported as-is, also to HTML format. However, most + browsers will treat a tab as a space, so it may not show up like expected if you view the exported + HTML file. The core export format of novelWriter is HTML5. You can also export the entire project as a single -novelWriter flavour document. In addition, other exports to Open Document, PDF, and plain text is -offered through the Qt library, although with limitations to formatting. +novelWriter Markdown-flavour document. In addition, other exports to Open Document, PDF, and plain +text is offered through the Qt library, although with limitations to formatting. ## Implementation The application is written in Python3 using Qt5 via PyQt5. It is developed on Linux, but it should -in principle work fine on other operating systems as well as long as dependencies are met. It is +in principle work fine on other operating systems as well, as long as dependencies are met. It is regularly tested on Windows 10. -The application can be started from the source folder with one of the commands: +The application can be started from the source folder with one of the commands, depending on your +Python configuration: ``` ./novelWriter.py python novelWriter.py @@ -158,7 +161,7 @@ pip install pyenchant ``` PyQt/Qt should be at least 5.3, but ideally 5.10 or higher for nearly all features to work. -Exporting to markdown requires PyQt/Qt 5.14. There are no known minimum for `lxml`, but the code +Exporting to Markdown requires PyQt/Qt 5.14. There are no known minimum for `lxml`, but the code was originally written with 4.2. The optional spell check library must be at least 3.0.0 to work with Windows 64 bit systems. On Linux, 2.0.0 also works fine. @@ -237,7 +240,7 @@ clickable in the document view pane, and control-clickable in the editor. They m quickly navigate between the documents while editing. -## Contribution +## Contributing If you want to contribute to novelWriter, please follow the coding convention laid out in the [Style Guide](markdown/style.md). They broadly follow Python PEP8, but there are a few diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index b613ac2f..f06eff22 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -205,7 +205,7 @@ class GuiDocViewer(QTextBrowser): "exist, or the index is out of date. The index can be updated " "from the Tools menu, or by pressing F9." ) % theTag, nwAlert.ERROR) - return + return False else: # Let the parent handle the opening as it also ensures that # the doc view panel is visible in case this request comes