Merge branch 'main' into new_project_wizard

This commit is contained in:
Veronica K. B. Olsen
2020-08-08 15:26:27 +02:00
41 changed files with 2006 additions and 806 deletions
+3 -3
View File
@@ -54,14 +54,14 @@ class GuiAbout(QDialog):
self.innerBox = QHBoxLayout()
self.innerBox.setSpacing(self.mainConf.pxInt(16))
self.setWindowTitle("About %s" % nw.__package__)
self.setWindowTitle("About %s" % self.mainConf.appName)
self.setMinimumWidth(self.mainConf.pxInt(650))
self.setMinimumHeight(self.mainConf.pxInt(600))
nPx = self.mainConf.pxInt(96)
self.nwIcon = QLabel()
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
self.lblName = QLabel("<b>%s</b>" % nw.__package__)
self.lblName = QLabel("<b>%s</b>" % self.mainConf.appName)
self.lblVers = QLabel("v%s" % nw.__version__)
self.lblDate = QLabel(datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x"))
@@ -133,7 +133,7 @@ class GuiAbout(QDialog):
"<h3>Credits</h3>"
"<p>{credits:s}</p>"
).format(
name = nw.__package__,
name = self.mainConf.appName,
copyright = nw.__copyright__,
website = nw.__url__,
domain = nw.__domain__,
-1
View File
@@ -430,7 +430,6 @@ class GuiBuildNovel(QDialog):
def _buildPreview(self):
"""Build a preview of the project in the document viewer.
"""
# Get Settings
fmtTitle = self.fmtTitle.text().strip()
fmtChapter = self.fmtChapter.text().strip()
+85 -23
View File
@@ -52,7 +52,7 @@ from nw.core import NWDoc, NWSpellSimple, countWords
from nw.gui.dochighlight import GuiDocHighlighter
from nw.common import transferCase
from nw.constants import (
nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwInsertSymbols, nwItemClass
nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwItemClass
)
logger = logging.getLogger(__name__)
@@ -79,6 +79,7 @@ class GuiDocEditor(QTextEdit):
self.wordCount = 0
self.paraCount = 0
self.lastEdit = 0
self.lastFind = None
self.bigDoc = False
self.doReplace = False
self.nonWord = "\"'"
@@ -89,7 +90,7 @@ class GuiDocEditor(QTextEdit):
self.typSQOpen = self.mainConf.fmtSingleQuotes[0]
self.typSQClose = self.mainConf.fmtSingleQuotes[1]
# Core Elements
# Core Elements and Signals
self.qDocument = self.document()
self.qDocument.contentsChange.connect(self._docChange)
@@ -590,8 +591,33 @@ class GuiDocEditor(QTextEdit):
"""
if isinstance(theInsert, str):
theText = theInsert
elif theInsert in nwInsertSymbols.SYMBOLS:
theText = nwInsertSymbols.SYMBOLS[theInsert]
elif theInsert in nwDocInsert:
if theInsert == nwDocInsert.NO_INSERT:
theText = "",
elif theInsert == nwDocInsert.HARD_BREAK:
theText = " \n",
elif theInsert == nwDocInsert.NB_SPACE:
theText = nwUnicode.U_NBSP,
elif theInsert == nwDocInsert.THIN_SPACE:
theText = nwUnicode.U_THNSP,
elif theInsert == nwDocInsert.THIN_NB_SPACE:
theText = nwUnicode.U_THNBSP,
elif theInsert == nwDocInsert.SHORT_DASH:
theText = nwUnicode.U_ENDASH,
elif theInsert == nwDocInsert.LONG_DASH:
theText = nwUnicode.U_EMDASH,
elif theInsert == nwDocInsert.ELLIPSIS:
theText = nwUnicode.U_HELLIP,
elif theInsert == nwDocInsert.QUOTE_LS:
theText = self.typSQOpen
elif theInsert == nwDocInsert.QUOTE_RS:
theText = self.typSQClose
elif theInsert == nwDocInsert.QUOTE_LD:
theText = self.typDQOpen
elif theInsert == nwDocInsert.QUOTE_RD:
theText = self.typDQClose
else:
return False
else:
return False
theCursor = self.textCursor()
@@ -660,6 +686,7 @@ class GuiDocEditor(QTextEdit):
triggers the syntax highlighter.
"""
self.lastEdit = time()
self.lastFind = None
if not self.docChanged:
self.setDocumentChanged(True)
if not self.wcTimer.isActive():
@@ -1145,19 +1172,21 @@ class GuiDocEditor(QTextEdit):
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
selText = theCursor.selectedText()
self.docSearch.setSearchText(theCursor.selectedText())
else:
selText = ""
self.docSearch.setSearchText(selText)
self.docSearch.setSearchText(None)
self.updateDocMargins()
return
def _beginReplace(self):
"""Opens the replace line of the search bar and sets the replace
text.
"""Opens the replace line of the search bar and sets the find
text if a selection has been made, and resets the replace text.
"""
self._beginSearch()
theCursor = self.textCursor()
if theCursor.hasSelection():
self.docSearch.setSearchText(theCursor.selectedText())
self.docSearch.setReplaceText("")
self.updateDocMargins()
return
def _findNext(self, isBackward=False):
@@ -1177,7 +1206,7 @@ class GuiDocEditor(QTextEdit):
if self.docSearch.isWholeWord:
findOpt |= QTextDocument.FindWholeWords
searchFor = self.docSearch.getSearchText()
searchFor = self.docSearch.getSearchObject()
wasFound = self.find(searchFor, findOpt)
if not wasFound:
if self.docSearch.doNextFile and not isBackward:
@@ -1190,7 +1219,11 @@ class GuiDocEditor(QTextEdit):
QTextCursor.End if isBackward else QTextCursor.Start
)
self.setTextCursor(theCursor)
self.find(searchFor, findOpt)
wasFound = self.find(searchFor, findOpt)
if wasFound:
theCursor = self.textCursor()
self.lastFind = (theCursor.selectionStart(), theCursor.selectionEnd())
return
@@ -1200,26 +1233,48 @@ class GuiDocEditor(QTextEdit):
next automatically when done.
"""
if not self.docSearch.isVisible():
# The search tool is not active, so we activate it.
self._beginSearch()
return
theCursor = self.textCursor()
if not theCursor.hasSelection():
# We have no text selected at all, so just make this a
# regular find next call.
self._findNext()
return
if self.lastFind is None and theCursor.hasSelection():
# If we have a selection but no search, it may have been the
# text we triggered the search with, in which case we search
# again from the beginning of that selection to make sure we
# have a valid result.
sPos = theCursor.selectionStart()
theCursor.clearSelection()
theCursor.setPosition(sPos)
self.setTextCursor(theCursor)
self._findNext()
theCursor = self.textCursor()
if self.lastFind is None:
# In case the above didn't find a result, we give up here.
return
searchFor = self.docSearch.getSearchText()
replWith = self.docSearch.getReplaceText()
selText = theCursor.selectedText()
if self.docSearch.doMatchCap:
replWith = transferCase(selText, replWith)
replWith = transferCase(theCursor.selectedText(), replWith)
if not self.docSearch.isCaseSense:
isMatch = searchFor.lower() == selText.lower()
else:
isMatch = searchFor == selText
# Make sure the selected text was selected by an actual find
# call, and not the user.
try:
isFind = self.lastFind[0] == theCursor.selectionStart()
isFind &= self.lastFind[1] == theCursor.selectionEnd()
except:
isFind = False
if isMatch:
if isFind:
theCursor.beginEditBlock()
theCursor.removeSelectedText()
theCursor.insertText(replWith)
@@ -1229,9 +1284,10 @@ class GuiDocEditor(QTextEdit):
logger.verbose("Replaced occurrence of '%s' with '%s' on line %d" % (
searchFor, replWith, theCursor.blockNumber()
))
else:
logger.error("The selected text is not a search result, skipping replace")
if searchFor:
self._findNext()
self._findNext()
return
@@ -1500,7 +1556,8 @@ class GuiDocEditSearch(QFrame):
"""
if not self.isVisible():
self.setVisible(True)
self.searchBox.setText(theText)
if theText is not None:
self.searchBox.setText(theText)
self.searchBox.setFocus()
if self.isRegEx:
self._alertSearchValid(True)
@@ -1515,7 +1572,7 @@ class GuiDocEditSearch(QFrame):
self.replaceBox.setText(theText)
return True
def getSearchText(self):
def getSearchObject(self):
"""Return the current search text either as text or as a regular
expression object.
"""
@@ -1543,6 +1600,11 @@ class GuiDocEditSearch(QFrame):
return theText
def getSearchText(self):
"""Return the current search text.
"""
return self.searchBox.text()
def getReplaceText(self):
"""Return the current replace text.
"""
+111 -17
View File
@@ -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
# Internals
self.assistProc = None
# Build Menu
self._buildProjectMenu()
self._buildDocumentMenu()
self._buildEditMenu()
@@ -67,15 +73,40 @@ class GuiMainMenu(QMenuBar):
return
##
# Methods
##
def setAvailableRoot(self):
for itemClass in nwItemClass:
if itemClass == nwItemClass.NO_CLASS: continue
if itemClass == nwItemClass.TRASH: continue
if itemClass == nwItemClass.NO_CLASS:
continue
if itemClass == nwItemClass.TRASH:
continue
self.rootItems[itemClass].setEnabled(
self.theProject.projTree.checkRootUnique(itemClass)
)
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 +165,26 @@ 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__))
if not self.mainConf.hasHelp:
self._openWebsite(nw.__docurl__)
return False
self.assistProc = QProcess(self)
self.assistProc.start("assistant", ["-collectionFile", self.mainConf.helpPath])
if not self.assistProc.waitForStarted(10000):
self._openWebsite(nw.__docurl__)
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):
@@ -258,7 +305,7 @@ class GuiMainMenu(QMenuBar):
# Project > Exit
self.aExitNW = QAction("Exit", self)
self.aExitNW.setStatusTip("Exit %s" % nw.__package__)
self.aExitNW.setStatusTip("Exit %s" % self.mainConf.appName)
self.aExitNW.setShortcut("Ctrl+Q")
self.aExitNW.triggered.connect(self._menuExit)
self.projMenu.addAction(self.aExitNW)
@@ -485,6 +532,37 @@ class GuiMainMenu(QMenuBar):
# Insert > Separator
self.insertMenu.addSeparator()
# Insert > Left Single Quote
self.aInsQuoteLS = QAction("Left Single Quote", self)
self.aInsQuoteLS.setStatusTip("Insert left single quote")
self.aInsQuoteLS.setShortcut("Ctrl+K, 1")
self.aInsQuoteLS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LS))
self.insertMenu.addAction(self.aInsQuoteLS)
# Insert > Right Single Quote
self.aInsQuoteRS = QAction("Right Single Quote", self)
self.aInsQuoteRS.setStatusTip("Insert right single quote")
self.aInsQuoteRS.setShortcut("Ctrl+K, 2")
self.aInsQuoteRS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RS))
self.insertMenu.addAction(self.aInsQuoteRS)
# Insert > Left Double Quote
self.aInsQuoteLD = QAction("Left Double Quote", self)
self.aInsQuoteLD.setStatusTip("Insert left double quote")
self.aInsQuoteLD.setShortcut("Ctrl+K, 3")
self.aInsQuoteLD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LD))
self.insertMenu.addAction(self.aInsQuoteLD)
# Insert > Right Double Quote
self.aInsQuoteRD = QAction("Right Double Quote", self)
self.aInsQuoteRD.setStatusTip("Insert right double quote")
self.aInsQuoteRD.setShortcut("Ctrl+K, 4")
self.aInsQuoteRD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RD))
self.insertMenu.addAction(self.aInsQuoteRD)
# Insert > Separator
self.insertMenu.addSeparator()
# Insert > Hard Line Break
self.aInsHardBreak = QAction("Hard Line Break", self)
self.aInsHardBreak.setStatusTip("Insert a hard line break")
@@ -773,8 +851,8 @@ class GuiMainMenu(QMenuBar):
self.helpMenu = self.addMenu("&Help")
# Help > About
self.aAboutNW = QAction("About %s" % nw.__package__, self)
self.aAboutNW.setStatusTip("About %s" % nw.__package__)
self.aAboutNW = QAction("About %s" % self.mainConf.appName, self)
self.aAboutNW.setStatusTip("About %s" % self.mainConf.appName)
self.aAboutNW.triggered.connect(self._showAbout)
self.helpMenu.addAction(self.aAboutNW)
@@ -787,17 +865,33 @@ class GuiMainMenu(QMenuBar):
# Help > Separator
self.helpMenu.addSeparator()
# Document > Preview
self.aHelp = QAction("Online Documentation", self)
self.aHelp.setStatusTip("View online documentation")
self.aHelp.setShortcut("F1")
self.aHelp.triggered.connect(self._openHelp)
self.helpMenu.addAction(self.aHelp)
# Document > Documentation
if self.mainConf.hasHelp and self.mainConf.hasAssistant:
self.aHelpLoc = QAction("Documentation (Local)", self)
self.aHelpLoc.setStatusTip("View local documentation with Qt Assistant")
self.aHelpLoc.triggered.connect(self._openAssistant)
self.aHelpLoc.setShortcut("F1")
self.helpMenu.addAction(self.aHelpLoc)
self.aHelpWeb = QAction("Documentation (Online)", self)
self.aHelpWeb.setStatusTip("View online documentation")
self.aHelpWeb.triggered.connect(lambda: self._openWebsite(nw.__docurl__))
if self.mainConf.hasHelp and self.mainConf.hasAssistant:
self.aHelpWeb.setShortcut("Shift+F1")
else:
self.aHelpWeb.setShortcuts(["F1","Shift+F1"])
self.helpMenu.addAction(self.aHelpWeb)
# 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
+7 -10
View File
@@ -108,7 +108,7 @@ class GuiPreferences(PagedDialog):
msgBox = QMessageBox()
msgBox.information(
self, "Preferences",
"Some changes will not be applied until %s has been restarted." % nw.__package__
"Some changes will not be applied until novelWriter has been restarted."
)
if validEntries:
@@ -154,7 +154,7 @@ class GuiConfigEditGeneralTab(QWidget):
self.mainForm.addRow(
"Main GUI theme",
self.selectTheme,
"Changing this requires restarting %s." % nw.__package__
"Changing this requires restarting novelWriter."
)
## Select Icon Theme
@@ -170,7 +170,7 @@ class GuiConfigEditGeneralTab(QWidget):
self.mainForm.addRow(
"Main icon theme",
self.selectIcons,
"Changing this requires restarting %s." % nw.__package__
"Changing this requires restarting novelWriter."
)
## Dark Icons
@@ -193,7 +193,7 @@ class GuiConfigEditGeneralTab(QWidget):
self.mainForm.addRow(
"Font family",
self.guiFont,
"Changing this requires restarting %s." % nw.__package__,
"Changing this requires restarting novelWriter.",
theButton = self.fontButton
)
@@ -206,7 +206,7 @@ class GuiConfigEditGeneralTab(QWidget):
self.mainForm.addRow(
"Font size",
self.guiFontSize,
"Changing this requires restarting %s." % nw.__package__,
"Changing this requires restarting novelWriter.",
theUnit = "pt"
)
@@ -625,13 +625,10 @@ class GuiConfigEditEditingTab(QWidget):
self.spellToolList = QComboBox(self)
self.spellToolList.addItem("Internal (difflib)", NWSpellCheck.SP_INTERNAL)
self.spellToolList.addItem("Spell Enchant (pyenchant)", NWSpellCheck.SP_ENCHANT)
# self.spellToolList.addItem("SymSpell (symspellpy)", NWSpellCheck.SP_SYMSPELL)
theModel = self.spellToolList.model()
idEnchant = self.spellToolList.findData(NWSpellCheck.SP_ENCHANT)
# idSymSpell = self.spellToolList.findData(NWSpellCheck.SP_SYMSPELL)
theModel = self.spellToolList.model()
idEnchant = self.spellToolList.findData(NWSpellCheck.SP_ENCHANT)
theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant)
# theModel.item(idSymSpell).setEnabled(self.mainConf.hasSymSpell)
self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool)
toolIdx = self.spellToolList.findData(self.mainConf.spellTool)