Merge pull request #786 from vkbo/allow_line_breaks

Allow line breaks
This commit is contained in:
Veronica Berglyd Olsen
2021-05-23 16:35:09 +02:00
committed by GitHub
33 changed files with 271 additions and 178 deletions
-1
View File
@@ -54,7 +54,6 @@ of writing novels. The formatting is currently limited to:
* Headings levels 1 to 4 using the `#` syntax only.
* Emphasised and strongly emphasised text. These are rendered as italicised and bold text.
* Strikethrough text.
* Hard line breaks using two or more spaces at the end of a line.
That is it. Features not supported in the editor are also not exported when using the export tool.
+1 -2
View File
@@ -38,8 +38,7 @@ at the same time provide a complete set of features needed for writing a novel.
.. tip::
If you do need to align information in rows and columns in your notes, you can achieve this with
tabs and hard line breaks. The tab stop width can be specified in :guilabel:`Preferences` and
hard line breaks can be inserted by adding to spaces at the end of the line.
tabs and line breaks. The tab stop width can be specified in :guilabel:`Preferences`.
The main window does not have a toolbar like many other applications do. This reduces clutter, and
since the documents are formatted with markdown tags, is more or less redundant. However, all
+35 -36
View File
@@ -205,15 +205,16 @@ tricky for languages that use the same symbol for these.
.. _a_ui_md:
Markdown Format
===============
The Markdown-Like Format
========================
The document editor uses a simplified markdown format. That is, it supports basic formatting like
emphasis (italic), strong importance (bold) and strikethrough text, as well as four levels of
headings.
The editor itself is a plaintext editor that uses formatting codes for setting meta data values and
allowing for some text formatting. The syntax is based on Markdown, but novelWriter is *not* a
Markdown editor. It supports basic formatting like emphasis (italic), strong importance (bold)
and strikethrough text, as well as four levels of headings.
Some non-standard markdown features have been added. For instance, novelWriter allows for comments,
a synopsis tag, and a set of keyword and value sets used for tags and references.
In addition to formatting codes, novelWriter allows for comments, a synopsis tag, and a set of
keyword and value sets used for tags and references.
.. _a_ui_md_head:
@@ -253,6 +254,33 @@ level of the novel. See :ref:`a_struct_heads` for more details.
itself. See :ref:`a_struct_heads_unnum` for more details.
.. _a_ui_md_text:
Text Paragraphs
---------------
A text paragraph is indicated by a blank line. That is, you need two line breaks to separate two
fragments of text into two paragraphs. Single line breaks are treated as line breaks within a
paragraph.
In addition, the editor supports a few additional types of whitespaces.
* A non-breaking space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Space`.
* Thin spaces are also supported, and can be inserted with :kbd:`Ctrl`:kbd:`K`,
:kbd:`Shift`:kbd:`Space`.
* Non-breaking thin space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`.
These are all insert features, and the :guilabel:`Insert` menu has more. They are also listed
in :ref:`a_ui_shortcuts_ins`.
Non-breaking spaces are highlighted by the syntax highlighter with an alternate coloured
background, depending on the selected theme.
.. tip::
Non-breaking spaces are the correct type of space to separate a number from its unit. Generally,
it prevents the line wrapping algorithms from adding line breaks where it shouldn't.
.. _a_ui_md_emph:
Text Emphasis
@@ -333,34 +361,6 @@ The available tag and reference keywords are listed in the :ref:`a_struct_tags`
also be inserted at the cursor position in the editor via the :guilabel:`Insert` menu.
.. _a_ui_md_add:
Additional Markdown and Non-Standard Features
---------------------------------------------
The editor and viewer also support markdown standard hard line breaks, and preserve non-breaking
spaces if running with Qt 5.9 or higher. For older versions, the non-breaking spaces are lost when
the document is saved. This is unfortunately hard-coded into the Qt text editor.
* A hard line break can be achieved by leaving two or more spaces at the end of the line. This is
standard markdown syntax. Alternatively, the user can press :kbd:`Ctrl`:kbd:`K`, :kbd:`Return` to
insert this type of line break.
* A non-breaking space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Space`.
* Thin spaces are also supported, and can be inserted with :kbd:`Ctrl`:kbd:`K`,
:kbd:`Shift`:kbd:`Space`.
* Non-breaking thin space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`.
These are all insert features, and the :guilabel:`Insert` menu has more. They are also listed
in :ref:`a_ui_shortcuts_ins`.
Both hard line breaks and non-breaking spaces are highlighted by the syntax highlighter as an
alternate coloured background, depending on the selected theme.
.. tip::
Non-breaking spaces are the correct type of space to separate a number from its unit. Generally,
it prevents the line wrapping algorithms from adding line breaks where it shouldn't.
.. _a_ui_outline:
Project Outline View
@@ -514,7 +514,6 @@ a key or key combination for the inserted content.
":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert an ellipsis."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`'`", "Insert a prime."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`""`", "Insert a double prime."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Return`", "Insert a hard line break."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Space`", "Insert a non-breaking space."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Shift`:kbd:`Space`", "Insert a thin space."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`", "Insert a thin non-breaking space."
+1 -1
View File
@@ -20,6 +20,6 @@ hidden = 150, 150, 150
keyword = 200, 46, 0
value = 184, 200, 0
spellcheckline = 200, 46, 0
tagerror = 46, 200, 0
errorline = 46, 200, 0
replacetag = 0, 184, 46
modifier = 200, 120, 0
+1 -1
View File
@@ -20,6 +20,6 @@ hidden = 100, 100, 100
keyword = 200, 50, 50
value = 50, 150, 50
spellcheckline = 200, 0, 0
tagerror = 0, 150, 0
errorline = 0, 150, 0
replacetag = 0, 150, 0
modifier = 150, 110, 30
+1 -1
View File
@@ -20,6 +20,6 @@ hidden = 150, 150, 150
keyword = 225, 225, 225
value = 200, 200, 200
spellcheckline = 200, 46, 0
tagerror = 46, 200, 0
errorline = 46, 200, 0
replacetag = 225, 225, 225
modifier = 225, 225, 225
+1 -1
View File
@@ -20,6 +20,6 @@ hidden = 100, 100, 100
keyword = 0, 0, 0
value = 20, 20, 20
spellcheckline = 200, 0, 0
tagerror = 0, 150, 0
errorline = 0, 150, 0
replacetag = 0, 0, 0
modifier = 0, 0, 0
+1 -1
View File
@@ -40,6 +40,6 @@ hidden = 152, 159, 177
keyword = 222, 61, 58
value = 150, 74, 193
spellcheckline = 222, 61, 58
tagerror = 8, 145, 106
errorline = 8, 145, 106
replacetag = 42, 162, 152
modifier = 224, 175, 5
+1 -1
View File
@@ -40,6 +40,6 @@ hidden = 99, 119, 119
keyword = 247, 140, 108
value = 199, 146, 234
spellcheckline = 247, 140, 108
tagerror = 173, 219, 103
errorline = 173, 219, 103
replacetag = 127, 219, 202
modifier = 236, 196, 141
+1 -1
View File
@@ -20,6 +20,6 @@ hidden = 147, 161, 161
keyword = 133, 153, 0
value = 203, 75, 22
spellcheckline = 203, 75, 22
tagerror = 220, 50, 47
errorline = 220, 50, 47
replacetag = 133, 153, 0
modifier = 181, 137, 0
+1 -1
View File
@@ -20,6 +20,6 @@ hidden = 88, 110, 117
keyword = 133, 153, 0
value = 203, 75, 22
spellcheckline = 203, 75, 22
tagerror = 220, 50, 47
errorline = 220, 50, 47
replacetag = 133, 153, 0
modifier = 181, 137, 0
+1 -1
View File
@@ -40,6 +40,6 @@ hidden = 142, 144, 140
keyword = 240, 40, 41
value = 137, 89, 168
spellcheckline = 240, 40, 41
tagerror = 113, 140, 0
errorline = 113, 140, 0
replacetag = 62, 153, 159
modifier = 245, 135, 31
+1 -1
View File
@@ -40,6 +40,6 @@ hidden = 150, 152, 150
keyword = 204, 102, 102
value = 178, 148, 187
spellcheckline = 204, 102, 102
tagerror = 181, 189, 104
errorline = 181, 189, 104
replacetag = 138, 190, 183
modifier = 222, 147, 95
@@ -40,6 +40,6 @@ hidden = 114, 133, 183
keyword = 255, 157, 164
value = 235, 187, 255
spellcheckline = 255, 157, 164
tagerror = 209, 241, 169
errorline = 209, 241, 169
replacetag = 153, 255, 255
modifier = 255, 197, 143
@@ -40,6 +40,6 @@ hidden = 150, 152, 150
keyword = 213, 78, 83
value = 195, 151, 216
spellcheckline = 213, 78, 83
tagerror = 185, 202, 74
errorline = 185, 202, 74
replacetag = 112, 192, 177
modifier = 231, 140, 69
@@ -40,6 +40,6 @@ hidden = 153, 153, 153
keyword = 242, 119, 122
value = 204, 153, 204
spellcheckline = 242, 119, 122
tagerror = 153, 204, 153
errorline = 153, 204, 153
replacetag = 102, 204, 204
modifier = 249, 145, 57
+5
View File
@@ -138,6 +138,7 @@ class Config:
self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in edior
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text
self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
@@ -602,6 +603,9 @@ class Config:
self.showLineEndings = self._parseLine(
cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings
)
self.showMultiSpaces = self._parseLine(
cnfParse, cnfSec, "showmultispaces", self.CNF_BOOL, self.showMultiSpaces
)
self.bigDocLimit = self._parseLine(
cnfParse, cnfSec, "bigdoclimit", self.CNF_INT, self.bigDocLimit
)
@@ -765,6 +769,7 @@ class Config:
cnfParse.set(cnfSec, "spellcheck", str(self.spellLanguage))
cnfParse.set(cnfSec, "showtabsnspaces", str(self.showTabsNSpaces))
cnfParse.set(cnfSec, "showlineendings", str(self.showLineEndings))
cnfParse.set(cnfSec, "showmultispaces", str(self.showMultiSpaces))
cnfParse.set(cnfSec, "bigdoclimit", str(self.bigDocLimit))
cnfParse.set(cnfSec, "showfullpath", str(self.showFullPath))
cnfParse.set(cnfSec, "highlightquotes", str(self.highlightQuotes))
+3 -9
View File
@@ -151,7 +151,6 @@ class ToHtml(Tokenizer):
thisPar = []
parStyle = None
tmpResult = []
hasHardBreak = False
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
@@ -196,16 +195,15 @@ class ToHtml(Tokenizer):
if tType == self.T_EMPTY:
if parStyle is None:
parStyle = ""
if hasHardBreak and self.cssStyles:
if len(thisPar) > 1 and self.cssStyles:
parClass = " class='break'"
else:
parClass = ""
if len(thisPar) > 0:
tTemp = "".join(thisPar)
tTemp = "<br/>".join(thisPar)
tmpResult.append("<p%s%s>%s</p>\n" % (parStyle, parClass, tTemp.rstrip()))
thisPar = []
parStyle = None
hasHardBreak = False
elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "<br/>")
@@ -239,11 +237,7 @@ class ToHtml(Tokenizer):
parStyle = hStyle
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos] + htmlTags[xFmt] + tTemp[xPos+xLen:]
if tText.endswith(" "):
thisPar.append(tTemp.rstrip() + "<br/>")
hasHardBreak = True
else:
thisPar.append(tTemp.rstrip() + " ")
thisPar.append(tTemp.rstrip())
elif tType == self.T_SYNOPSIS and self.doSynopsis:
tmpResult.append(self._formatSynopsis(tText))
+3 -6
View File
@@ -89,12 +89,12 @@ class ToMarkdown(Tokenizer):
thisPar = []
tmpResult = []
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
for tType, _, tText, tFormat, tStyle in self.theTokens:
# Process Text Type
if tType == self.T_EMPTY:
if len(thisPar) > 0:
tTemp = "".join(thisPar)
tTemp = " \n".join(thisPar)
tmpResult.append("%s\n\n" % tTemp.rstrip(" "))
thisPar = []
@@ -128,10 +128,7 @@ class ToMarkdown(Tokenizer):
tTemp = tText
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos] + mdTags[xFmt] + tTemp[xPos+xLen:]
if tText.endswith(" "):
thisPar.append(tTemp.rstrip() + " \n")
else:
thisPar.append(tTemp.rstrip() + " ")
thisPar.append(tTemp.rstrip())
elif tType == self.T_SYNOPSIS and self.doSynopsis:
tmpResult.append("**%s:** %s\n\n" % (self._localLookup("Synopsis"), tText))
+8 -16
View File
@@ -327,8 +327,7 @@ class ToOdt(Tokenizer):
thisPar = []
thisFmt = []
parStyle = None
hasHardBreak = False
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
for tType, _, tText, tFormat, tStyle in self.theTokens:
# Styles
oStyle = ODTParagraphStyle()
@@ -359,13 +358,13 @@ class ToOdt(Tokenizer):
# Process Text Types
if tType == self.T_EMPTY:
if hasHardBreak and parStyle is not None:
if len(thisPar) > 1 and parStyle is not None:
if self.doJustify:
parStyle.setTextAlign("left")
if len(thisPar) > 0:
tTemp = "".join(thisPar)
fTemp = "".join(thisFmt)
tTemp = "\n".join(thisPar)
fTemp = " ".join(thisFmt)
tTxt = tTemp.rstrip()
tFmt = fTemp[:len(tTxt)]
self._addTextPar("Text_Body", parStyle, tTxt, theFmt=tFmt)
@@ -373,7 +372,6 @@ class ToOdt(Tokenizer):
thisPar = []
thisFmt = []
parStyle = None
hasHardBreak = False
elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "\n")
@@ -402,23 +400,17 @@ class ToOdt(Tokenizer):
self._addTextPar("Text_Body", oStyle, "")
elif tType == self.T_TEXT:
tTemp = tText
if parStyle is None:
parStyle = oStyle
tFmt = " "*len(tTemp)
tFmt = " "*len(tText)
for xPos, xLen, xFmt in tFormat:
tFmt = tFmt[:xPos] + odtTags[xFmt] + tFmt[xPos+xLen:]
tTxt = tTemp.rstrip()
tTxt = tText.rstrip()
tFmt = tFmt[:len(tTxt)]
if tText.endswith(" "):
thisPar.append(tTxt + "\n")
thisFmt.append(tFmt + " ")
hasHardBreak = True
else:
thisPar.append(tTxt + " ")
thisFmt.append(tFmt + " ")
thisPar.append(tTxt)
thisFmt.append(tFmt)
elif tType == self.T_SYNOPSIS and self.doSynopsis:
tTemp, fTemp = self._formatSynopsis(tText)
+16
View File
@@ -924,6 +924,19 @@ class GuiPreferencesSyntax(QWidget):
self.tr("Applies to emphasis (italic) and strong (bold).")
)
# Text Errors
# ===========
self.mainForm.addGroupLabel(self.tr("Text Errors"))
self.showMultiSpaces = QSwitch()
self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
self.mainForm.addRow(
self.tr("Mark redundant spaces"),
self.showMultiSpaces,
self.tr("Trailing spaces or multiple spaces between words.")
)
return
def saveValues(self):
@@ -940,6 +953,9 @@ class GuiPreferencesSyntax(QWidget):
# Text Emphasis
self.mainConf.highlightEmph = self.highlightEmph.isChecked()
# Text Errors
self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked()
self.mainConf.confChanged = True
return
+5 -5
View File
@@ -89,17 +89,17 @@ class nwDocAction(Enum):
BLOCK_TXT = 18
REPL_SNG = 19
REPL_DBL = 20
RM_BREAKS = 21
# END Enum nwDocAction
class nwDocInsert(Enum):
NO_INSERT = 0
HARD_BREAK = 1
QUOTE_LS = 2
QUOTE_RS = 3
QUOTE_LD = 4
QUOTE_RD = 5
QUOTE_LS = 1
QUOTE_RS = 2
QUOTE_LD = 3
QUOTE_RD = 4
# END Enum nwDocInsert
+51 -3
View File
@@ -791,6 +791,8 @@ class GuiDocEditor(QTextEdit):
self._replaceQuotes("'", self._typSQOpen, self._typSQClose)
elif theAction == nwDocAction.REPL_DBL:
self._replaceQuotes("\"", self._typDQOpen, self._typDQClose)
elif theAction == nwDocAction.RM_BREAKS:
self._removeInParLineBreaks()
else:
logger.debug("Unknown or unsupported document action %s" % str(theAction))
self._allowAutoReplace(True)
@@ -840,9 +842,7 @@ class GuiDocEditor(QTextEdit):
if isinstance(theInsert, str):
theText = theInsert
elif isinstance(theInsert, nwDocInsert):
if theInsert == nwDocInsert.HARD_BREAK:
theText = " \n"
elif theInsert == nwDocInsert.QUOTE_LS:
if theInsert == nwDocInsert.QUOTE_LS:
theText = self._typSQOpen
elif theInsert == nwDocInsert.QUOTE_RS:
theText = self._typSQClose
@@ -1787,6 +1787,54 @@ class GuiDocEditor(QTextEdit):
return True
def _removeInParLineBreaks(self):
"""Strip line breaks within paragraphs in the selected text.
"""
theCursor = self.textCursor()
theDoc = self.document()
iS = 0
iE = theDoc.blockCount() - 1
rS = 0
rE = theDoc.characterCount()
if theCursor.hasSelection():
sBlock = theDoc.findBlock(theCursor.selectionStart())
eBlock = theDoc.findBlock(theCursor.selectionEnd())
iS = sBlock.blockNumber()
iE = eBlock.blockNumber()
rS = sBlock.position()
rE = eBlock.position() + eBlock.length()
# Clean up the text
currPar = []
cleanText = ""
for i in range(iS, iE+1):
cBlock = theDoc.findBlockByNumber(i)
cText = cBlock.text()
if cText.strip() == "":
if currPar:
cleanText += " ".join(currPar) + "\n\n"
else:
cleanText += "\n"
currPar = []
elif cText.startswith(("# ", "## ", "### ", "#### ", "@", "%")):
cleanText += cText + "\n"
else:
currPar.append(cText)
if currPar:
cleanText += " ".join(currPar) + "\n\n"
# Replace the text with the cleaned up text
theCursor.beginEditBlock()
theCursor.clearSelection()
theCursor.setPosition(rS)
theCursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, rE-rS)
theCursor.insertText(cleanText.rstrip() + "\n")
theCursor.endEditBlock()
return True
def _makeSelection(self, selMode):
"""Wrapper function to select text based on a selection mode.
"""
+18 -14
View File
@@ -71,7 +71,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colKey = QColor(0, 0, 0)
self.colVal = QColor(0, 0, 0)
self.colSpell = QColor(0, 0, 0)
self.colTagErr = QColor(0, 0, 0)
self.colError = QColor(0, 0, 0)
self.colRepTag = QColor(0, 0, 0)
self.initHighlighter()
@@ -95,11 +95,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colKey = QColor(*self.theTheme.colKey)
self.colVal = QColor(*self.theTheme.colVal)
self.colSpell = QColor(*self.theTheme.colSpell)
self.colTagErr = QColor(*self.theTheme.colTagErr)
self.colError = QColor(*self.theTheme.colError)
self.colRepTag = QColor(*self.theTheme.colRepTag)
self.colMod = QColor(*self.theTheme.colMod)
self.colTrail = QColor(*self.theTheme.colEmph)
self.colTrail.setAlpha(64)
self.colBreak = QColor(*self.theTheme.colEmph)
self.colBreak.setAlpha(64)
self.colEmph = None
if self.mainConf.highlightEmph:
@@ -117,8 +117,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"bold" : self._makeFormat(self.colEmph, "bold"),
"italic" : self._makeFormat(self.colEmph, "italic"),
"strike" : self._makeFormat(self.colHidden, "strike"),
"trailing" : self._makeFormat(self.colTrail, "background"),
"nobreak" : self._makeFormat(self.colTrail, "background"),
"mspaces" : self._makeFormat(self.colError, "errline"),
"nobreak" : self._makeFormat(self.colBreak, "background"),
"dialogue1" : self._makeFormat(self.colDialN),
"dialogue2" : self._makeFormat(self.colDialD),
"dialogue3" : self._makeFormat(self.colDialS),
@@ -131,12 +131,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.hRules = []
# Trailing Spaces, 2+
self.hRules.append((
r"[ ]{2,}$", {
0 : self.hStyles["trailing"],
}
))
# Multiple or Trailing Spaces
if self.mainConf.showMultiSpaces:
self.hRules.append((
r"[ ]{2,}|[ ]*$", {
0 : self.hStyles["mspaces"],
}
))
# Non-Breaking Spaces
self.hRules.append((
@@ -174,7 +175,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
}
))
# Markdown
# Markdown Syntax
self.hRules.append((
nwRegEx.FMT_EI, {
1 : self.hStyles["hidden"],
@@ -296,7 +297,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(xPos, xLen, self.hStyles["value"])
else:
kwFmt = self.format(xPos)
kwFmt.setUnderlineColor(self.colTagErr)
kwFmt.setUnderlineColor(self.colError)
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
self.setFormat(xPos, xLen, kwFmt)
@@ -391,6 +392,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
theFormat.setFontItalic(True)
if "strike" in fmtStyle:
theFormat.setFontStrikeOut(True)
if "errline" in fmtStyle:
theFormat.setUnderlineColor(self.colError)
theFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
if "underline" in fmtStyle:
theFormat.setFontUnderline(True)
if "background" in fmtStyle:
+64 -63
View File
@@ -634,15 +634,8 @@ class GuiMainMenu(QMenuBar):
self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME))
self.mInsPunct.addAction(self.aInsDPrime)
# Insert > Breaks and Spaces
self.mInsBreaks = self.insertMenu.addMenu(self.tr("Breaks and Spaces"))
# Insert > Hard Line Break
self.aInsHardBreak = QAction(self.tr("Hard Line Break"), self)
self.aInsHardBreak.setStatusTip(self.tr("Insert a hard line break"))
self.aInsHardBreak.setShortcut("Ctrl+K, Return")
self.aInsHardBreak.triggered.connect(lambda: self._docInsert(nwDocInsert.HARD_BREAK))
self.mInsBreaks.addAction(self.aInsHardBreak)
# Insert > White Spaces
self.mInsBreaks = self.insertMenu.addMenu(self.tr("White Spaces"))
# Insert > Non-Breaking Space
self.aInsNBSpace = QAction(self.tr("Non-Breaking Space"), self)
@@ -750,60 +743,6 @@ class GuiMainMenu(QMenuBar):
return
def _buildSearchMenu(self):
"""Assemble the Search menu.
"""
# Search
self.srcMenu = self.addMenu(self.tr("&Search"))
# Search > Find
self.aFind = QAction(self.tr("Find"), self)
self.aFind.setStatusTip(self.tr("Find text in document"))
self.aFind.setShortcut("Ctrl+F")
self.aFind.triggered.connect(lambda: self.theParent.docEditor.beginSearch())
self.srcMenu.addAction(self.aFind)
# Search > Replace
self.aReplace = QAction(self.tr("Replace"), self)
self.aReplace.setStatusTip(self.tr("Replace text in document"))
if self.mainConf.osDarwin:
self.aReplace.setShortcut("Ctrl+=")
else:
self.aReplace.setShortcut("Ctrl+H")
self.aReplace.triggered.connect(lambda: self.theParent.docEditor.beginReplace())
self.srcMenu.addAction(self.aReplace)
# Search > Find Next
self.aFindNext = QAction(self.tr("Find Next"), self)
self.aFindNext.setStatusTip(self.tr("Find next occurrence of text in document"))
if self.mainConf.osDarwin:
self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
else:
self.aFindNext.setShortcuts(["F3", "Ctrl+G"])
self.aFindNext.triggered.connect(lambda: self.theParent.docEditor.findNext())
self.srcMenu.addAction(self.aFindNext)
# Search > Find Prev
self.aFindPrev = QAction(self.tr("Find Previous"), self)
self.aFindPrev.setStatusTip(self.tr("Find previous occurrence of text in document"))
if self.mainConf.osDarwin:
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
else:
self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"])
self.aFindPrev.triggered.connect(lambda: self.theParent.docEditor.findNext(goBack=True))
self.srcMenu.addAction(self.aFindPrev)
# Search > Replace Next
self.aReplaceNext = QAction(self.tr("Replace Next"), self)
self.aReplaceNext.setStatusTip(
self.tr("Find and replace next occurrence of text in document")
)
self.aReplaceNext.setShortcut("Ctrl+Shift+1")
self.aReplaceNext.triggered.connect(lambda: self.theParent.docEditor.replaceNext())
self.srcMenu.addAction(self.aReplaceNext)
return
def _buildFormatMenu(self):
"""Assemble the Format menu.
"""
@@ -912,6 +851,68 @@ class GuiMainMenu(QMenuBar):
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
self.fmtMenu.addAction(self.aFmtReplDbl)
# Format > Remove In-Paragraph Breaks
self.aFmtRmBreaks = QAction(self.tr("Remove In-Paragraph Breaks"), self)
self.aFmtRmBreaks.setStatusTip(
self.tr("Removes all line breaks within paragraphs in the selected text")
)
self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS))
self.fmtMenu.addAction(self.aFmtRmBreaks)
return
def _buildSearchMenu(self):
"""Assemble the Search menu.
"""
# Search
self.srcMenu = self.addMenu(self.tr("&Search"))
# Search > Find
self.aFind = QAction(self.tr("Find"), self)
self.aFind.setStatusTip(self.tr("Find text in document"))
self.aFind.setShortcut("Ctrl+F")
self.aFind.triggered.connect(lambda: self.theParent.docEditor.beginSearch())
self.srcMenu.addAction(self.aFind)
# Search > Replace
self.aReplace = QAction(self.tr("Replace"), self)
self.aReplace.setStatusTip(self.tr("Replace text in document"))
if self.mainConf.osDarwin:
self.aReplace.setShortcut("Ctrl+=")
else:
self.aReplace.setShortcut("Ctrl+H")
self.aReplace.triggered.connect(lambda: self.theParent.docEditor.beginReplace())
self.srcMenu.addAction(self.aReplace)
# Search > Find Next
self.aFindNext = QAction(self.tr("Find Next"), self)
self.aFindNext.setStatusTip(self.tr("Find next occurrence of text in document"))
if self.mainConf.osDarwin:
self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
else:
self.aFindNext.setShortcuts(["F3", "Ctrl+G"])
self.aFindNext.triggered.connect(lambda: self.theParent.docEditor.findNext())
self.srcMenu.addAction(self.aFindNext)
# Search > Find Prev
self.aFindPrev = QAction(self.tr("Find Previous"), self)
self.aFindPrev.setStatusTip(self.tr("Find previous occurrence of text in document"))
if self.mainConf.osDarwin:
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
else:
self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"])
self.aFindPrev.triggered.connect(lambda: self.theParent.docEditor.findNext(goBack=True))
self.srcMenu.addAction(self.aFindPrev)
# Search > Replace Next
self.aReplaceNext = QAction(self.tr("Replace Next"), self)
self.aReplaceNext.setStatusTip(
self.tr("Find and replace next occurrence of text in document")
)
self.aReplaceNext.setShortcut("Ctrl+Shift+1")
self.aReplaceNext.triggered.connect(lambda: self.theParent.docEditor.replaceNext())
self.srcMenu.addAction(self.aReplaceNext)
return
def _buildToolsMenu(self):
+2 -2
View File
@@ -106,7 +106,7 @@ class GuiTheme:
self.colKey = [0, 0, 0]
self.colVal = [0, 0, 0]
self.colSpell = [0, 0, 0]
self.colTagErr = [0, 0, 0]
self.colError = [0, 0, 0]
self.colRepTag = [0, 0, 0]
self.colMod = [0, 0, 0]
@@ -373,7 +373,7 @@ class GuiTheme:
self.colKey = self._loadColour(confParser, cnfSec, "keyword")
self.colVal = self._loadColour(confParser, cnfSec, "value")
self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline")
self.colTagErr = self._loadColour(confParser, cnfSec, "tagerror")
self.colError = self._loadColour(confParser, cnfSec, "errorline")
self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
self.colMod = self._loadColour(confParser, cnfSec, "modifier")
-1
View File
@@ -1318,7 +1318,6 @@ class GuiMain(QMainWindow):
self.addAction(self.mainMenu.aInsEllipsis)
self.addAction(self.mainMenu.aInsPrime)
self.addAction(self.mainMenu.aInsDPrime)
self.addAction(self.mainMenu.aInsHardBreak)
self.addAction(self.mainMenu.aInsNBSpace)
self.addAction(self.mainMenu.aInsThinSpace)
self.addAction(self.mainMenu.aInsThinNBSpace)
@@ -55,6 +55,7 @@ spelltool = internal
spellcheck = en
showtabsnspaces = False
showlineendings = False
showmultispaces = True
bigdoclimit = 800
showfullpath = True
highlightquotes = True
@@ -55,6 +55,7 @@ spelltool = internal
spellcheck = en
showtabsnspaces = True
showlineendings = True
showmultispaces = True
bigdoclimit = 500
showfullpath = False
highlightquotes = False
+2 -1
View File
@@ -93,7 +93,8 @@ def testCoreToOdt_Convert(dummyGUI):
'<text:span text:style-name="T2">bold</text:span>'
'<text:span text:style-name="T1"> and </text:span>'
'<text:span text:style-name="T3">italics</text:span>'
'<text:span text:style-name="T1"> text</text:span> text. No format</text:p>'
'<text:span text:style-name="T1"> text</text:span> text.'
'<text:line-break/>No format</text:p>'
'</office:text>'
)
+1 -1
View File
@@ -40,7 +40,7 @@ typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir):
def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
"""Test the document editor.
"""
# Block message box
+41 -4
View File
@@ -236,6 +236,47 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
"Also text with “double” quotes which are “less tricky”.\n\n"
)
# Remove in-paragraph line breaks
nwGUI.docEditor.setText((
"### New Text\n\n"
"@char: Someone\n"
"@location: Somewhere\n\n"
"% Some comment ...\n\n"
"Here is some text\non multiple\nlines.\n\n"
"With another paragraph\nhere."
))
nwGUI.mainMenu.aFmtRmBreaks.activate(QAction.Trigger)
assert nwGUI.docEditor.getText() == (
"### New Text\n\n"
"@char: Someone\n"
"@location: Somewhere\n\n"
"% Some comment ...\n\n"
"Here is some text on multiple lines.\n\n"
"With another paragraph here.\n"
)
nwGUI.docEditor.setText((
"### New Text\n\n"
"@char: Someone\n"
"@location: Somewhere\n\n"
"% Some comment ...\n\n"
"Here is some text\non multiple\nlines.\n\n"
"With another paragraph\nhere."
))
theCursor = nwGUI.docEditor.textCursor()
theCursor.setPosition(74)
theCursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 29)
nwGUI.docEditor.setTextCursor(theCursor)
nwGUI.mainMenu.aFmtRmBreaks.activate(QAction.Trigger)
assert nwGUI.docEditor.getText() == (
"### New Text\n\n"
"@char: Someone\n"
"@location: Somewhere\n\n"
"% Some comment ...\n\n"
"Here is some text on multiple lines.\n\n"
"With another paragraph\nhere."
)
# Test Invalid Document Action
assert not nwGUI.docEditor.docAction(nwDocAction.NO_ACTION)
@@ -467,10 +508,6 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert nwGUI.docEditor.getText() == nwUnicode.U_DIVIDE
nwGUI.docEditor.clear()
nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger)
assert nwGUI.docEditor.getText() == " \n"
nwGUI.docEditor.clear()
nwGUI.mainMenu.aInsNBSpace.activate(QAction.Trigger)
if nwGUI.mainConf.verQtValue >= 50900:
assert nwGUI.docEditor.getText() == nwUnicode.U_NBSP
+1 -1
View File
@@ -109,7 +109,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert nwGUI.theTheme.colKey == [242, 119, 122]
assert nwGUI.theTheme.colVal == [204, 153, 204]
assert nwGUI.theTheme.colSpell == [242, 119, 122]
assert nwGUI.theTheme.colTagErr == [153, 204, 153]
assert nwGUI.theTheme.colError == [153, 204, 153]
assert nwGUI.theTheme.colRepTag == [102, 204, 204]
assert nwGUI.theTheme.colMod == [249, 145, 57]