Rename package from 'nw' to 'novelwriter' (#868)

* Rename nw folder to novelwriter
* Rename nw to novelwriter in auxiliary files
* Rename nw to novelwriter in main app source
* Rename nw to novelwriter in tests
* Make setup script for pdf docs less spammy
This commit is contained in:
Veronica Berglyd Olsen
2021-08-26 17:33:54 +02:00
committed by GitHub
parent 2cf2eb9f55
commit 3403d98c72
396 changed files with 595 additions and 566 deletions
+44
View File
@@ -0,0 +1,44 @@
"""
novelWriter Dialogs Init
==========================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from novelwriter.dialogs.about import GuiAbout
from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.itemeditor import GuiItemEditor
from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.projload import GuiProjectLoad
from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.dialogs.updates import GuiUpdates
from novelwriter.dialogs.wordlist import GuiWordList
__all__ = [
"GuiAbout",
"GuiDocMerge",
"GuiDocSplit",
"GuiItemEditor",
"GuiPreferences",
"GuiProjectLoad",
"GuiProjectSettings",
"GuiQuoteSelect",
"GuiUpdates",
"GuiWordList",
]
+297
View File
@@ -0,0 +1,297 @@
"""
novelWriter GUI About Box
===========================
The about novelWriter dialog box
File History:
Created: 2020-05-21 [0.5.2]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import logging
import novelwriter
from datetime import datetime
from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QTabWidget,
QTextBrowser, QLabel
)
logger = logging.getLogger(__name__)
class GuiAbout(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiAbout ...")
self.setObjectName("GuiAbout")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.innerBox.setSpacing(self.mainConf.pxInt(16))
self.setWindowTitle(self.tr("About novelWriter"))
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>novelWriter</b>")
self.lblVers = QLabel("v%s" % novelwriter.__version__)
self.lblDate = QLabel(datetime.strptime(novelwriter.__date__, "%Y-%m-%d").strftime("%x"))
self.leftBox = QVBoxLayout()
self.leftBox.setSpacing(self.mainConf.pxInt(4))
self.leftBox.addWidget(self.nwIcon, 0, Qt.AlignCenter)
self.leftBox.addWidget(self.lblName, 0, Qt.AlignCenter)
self.leftBox.addWidget(self.lblVers, 0, Qt.AlignCenter)
self.leftBox.addWidget(self.lblDate, 0, Qt.AlignCenter)
self.leftBox.addStretch(1)
self.innerBox.addLayout(self.leftBox)
# Pages
self.pageAbout = QTextBrowser()
self.pageAbout.setOpenExternalLinks(True)
self.pageAbout.document().setDocumentMargin(self.mainConf.pxInt(16))
self.pageNotes = QTextBrowser()
self.pageNotes.setOpenExternalLinks(True)
self.pageNotes.document().setDocumentMargin(self.mainConf.pxInt(16))
self.pageLicense = QTextBrowser()
self.pageLicense.setOpenExternalLinks(True)
self.pageLicense.document().setDocumentMargin(self.mainConf.pxInt(16))
# Main Tab Area
self.tabBox = QTabWidget()
self.tabBox.addTab(self.pageAbout, self.tr("About"))
self.tabBox.addTab(self.pageNotes, self.tr("Release"))
self.tabBox.addTab(self.pageLicense, self.tr("Licence"))
self.innerBox.addWidget(self.tabBox)
# OK Button
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
self.buttonBox.accepted.connect(self._doClose)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
logger.debug("GuiAbout initialisation complete")
return
def populateGUI(self):
"""Populate tabs with text.
"""
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
self._setStyleSheet()
self._fillAboutPage()
self._fillNotesPage()
self._fillLicensePage()
qApp.restoreOverrideCursor()
return
def showReleaseNotes(self):
"""Show the release notes.
"""
self.tabBox.setCurrentWidget(self.pageNotes)
return
##
# Internal Functions
##
def _fillAboutPage(self):
"""Generate the content for the About page.
"""
aboutMsg = (
"<h2>{title1}</h2>"
"<p>{copy}</p>"
"<p>{link}</p>"
"<p>{intro}</p>"
"<p>{license1}</p>"
"<p>{license2}</p>"
"<p>{license3}</p>"
"<h3>{title2}</h3>"
"<p>{credits}</p>"
).format(
title1=self.tr("About novelWriter"),
copy=novelwriter.__copyright__,
link=self.tr("Website: {0}").format(
f"<a href='{novelwriter.__url__}'>{novelwriter.__domain__}</a>"
),
title2=self.tr("Credits"),
credits=self._wrapTable([
(self.tr("Developer"), "Veronica Berglyd Olsen"),
(self.tr("Concept"), "Veronica Berglyd Olsen, Marian Lückhof"),
(self.tr("i18n"), "Bruno Meneguello"),
]),
intro=self.tr(
"novelWriter is a markdown-like text editor designed for organising and "
"writing novels. It is written in Python 3 with a Qt5 GUI, using PyQt5."
),
license1=self.tr(
"novelWriter is free software: you can redistribute it and/or modify it "
"under the terms of the GNU General Public License as published by the "
"Free Software Foundation, either version 3 of the License, or (at your "
"option) any later version."
),
license2=self.tr(
"novelWriter is distributed in the hope that it will be useful, but "
"WITHOUT ANY WARRANTY; without even the implied warranty of "
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
),
license3=self.tr(
"See the Licence tab for the full licence text, or visit the "
"GNU website at {0} for more details."
).format(
"<a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPL v3.0</a>"
),
)
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
self.tr("Translations"),
self._wrapTable([
("English", "Veronica Berglyd Olsen"),
("Français", "Jan Lüdke (jyhelle)"),
("Norsk Bokmål", "Veronica Berglyd Olsen"),
("Português", "Bruno Meneguello"),
("简体中文", "Qianzhi Long"),
])
)
theTheme = self.theParent.theTheme
theIcons = self.theParent.theTheme.theIcons
if theTheme.themeName and theTheme.themeAuthor != "N/A":
licURL = f"<a href='{theTheme.themeLicenseUrl}'>{theTheme.themeLicense}</a>"
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
self.tr("Theme: {0}").format(theTheme.themeName),
self._wrapTable([
(self.tr("Author"), theTheme.themeAuthor),
(self.tr("Credit"), theTheme.themeCredit),
(self.tr("Licence"), licURL),
])
)
if theIcons.themeName:
licURL = f"<a href='{theIcons.themeLicenseUrl}'>{theIcons.themeLicense}</a>"
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
self.tr("Icons: {0}").format(theIcons.themeName),
self._wrapTable([
(self.tr("Author"), theIcons.themeAuthor),
(self.tr("Credit"), theIcons.themeCredit),
(self.tr("Licence"), licURL),
])
)
if theTheme.syntaxName:
licURL = f"<a href='{theTheme.syntaxLicenseUrl}'>{theTheme.syntaxLicense}</a>"
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
self.tr("Syntax: {0}").format(theTheme.syntaxName),
self._wrapTable([
(self.tr("Author"), theTheme.syntaxAuthor),
(self.tr("Credit"), theTheme.syntaxCredit),
(self.tr("Licence"), licURL),
])
)
self.pageAbout.setHtml(aboutMsg)
return
def _fillNotesPage(self):
"""Load the content for the Release Notes page.
"""
docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm")
if os.path.isfile(docPath):
with open(docPath, mode="r", encoding="utf-8") as inFile:
helpText = inFile.read()
self.pageNotes.setHtml(helpText)
else:
self.pageNotes.setHtml("Error loading release notes text ...")
return
def _fillLicensePage(self):
"""Load the content for the Licence page.
"""
docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm")
if os.path.isfile(docPath):
with open(docPath, mode="r", encoding="utf-8") as inFile:
helpText = inFile.read()
self.pageLicense.setHtml(helpText)
else:
self.pageLicense.setHtml("Error loading licence text ...")
return
def _wrapTable(self, theData):
"""Wrap a list of label/value tuples in a html table.
"""
theTable = []
for aLabel, aValue in theData:
theTable.append(
f"<tr><td><b>{aLabel}:</b></td><td>{aValue}</td></tr>"
)
return "<table>%s</table>" % "".join(theTable)
def _setStyleSheet(self):
"""Set stylesheet for all browser tabs
"""
styleSheet = (
"h1, h2, h3, h4 {{"
" color: rgb({hColR},{hColG},{hColB});"
"}}\n"
"a {{"
" color: rgb({hColR},{hColG},{hColB});"
"}}\n"
".alt {{"
" color: rgb({kColR},{kColG},{kColB});"
"}}\n"
"td {{"
" padding-right: 0.8em;"
"}}\n"
).format(
hColR=self.theParent.theTheme.colHead[0],
hColG=self.theParent.theTheme.colHead[1],
hColB=self.theParent.theTheme.colHead[2],
kColR=self.theTheme.colKey[0],
kColG=self.theTheme.colKey[1],
kColB=self.theTheme.colKey[2],
)
self.pageAbout.document().setDefaultStyleSheet(styleSheet)
self.pageNotes.document().setDefaultStyleSheet(styleSheet)
self.pageLicense.document().setDefaultStyleSheet(styleSheet)
return
def _doClose(self):
self.close()
return
# END Class GuiAbout
+193
View File
@@ -0,0 +1,193 @@
"""
novelWriter GUI Doc Merge Tool
================================
GUI class for merging multiple documents to one document
File History:
Created: 2020-01-23 [0.4.3]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import novelwriter
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QLabel, QListWidget, QAbstractItemView,
QListWidgetItem, QDialogButtonBox
)
from novelwriter.core import NWDoc
from novelwriter.enum import nwAlert, nwItemType
from novelwriter.gui.custom import QHelpLabel
logger = logging.getLogger(__name__)
class GuiDocMerge(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiDocMerge ...")
self.setObjectName("GuiDocMerge")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.sourceItem = None
self.outerBox = QVBoxLayout()
self.setWindowTitle(self.tr("Merge Documents"))
self.headLabel = QLabel("<b>%s</b>" % self.tr("Documents to Merge"))
self.helpLabel = QHelpLabel(
self.tr("Drag and drop items to change the order."), self.theParent.theTheme.helpText
)
self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doMerge)
self.buttonBox.rejected.connect(self._doClose)
self.outerBox.setSpacing(0)
self.outerBox.addWidget(self.headLabel)
self.outerBox.addWidget(self.helpLabel)
self.outerBox.addSpacing(self.mainConf.pxInt(8))
self.outerBox.addWidget(self.listBox)
self.outerBox.addSpacing(self.mainConf.pxInt(12))
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
self.rejected.connect(self._doClose)
self._populateList()
logger.debug("GuiDocMerge initialisation complete")
return
##
# Buttons
##
def _doMerge(self):
"""Perform the merge of the files in the selected folder, and
create a new file in the same parent folder. The old files are
not removed in the merge process, and must be deleted manually.
"""
logger.verbose("GuiDocMerge merge button clicked")
finalOrder = []
for i in range(self.listBox.count()):
finalOrder.append(self.listBox.item(i).data(Qt.UserRole))
if len(finalOrder) == 0:
self.theParent.makeAlert(self.tr(
"No source documents found. Nothing to do."
), nwAlert.ERROR)
return False
theText = ""
for tHandle in finalOrder:
inDoc = NWDoc(self.theProject, tHandle)
docText = inDoc.readDocument()
docErr = inDoc.getError()
if docText is None and docErr:
self.theParent.makeAlert([
self.tr("Failed to open document file."), docErr
], nwAlert.ERROR)
if docText:
theText += docText.rstrip("\n")+"\n\n"
if self.sourceItem is None:
self.theParent.makeAlert(self.tr(
"No source folder selected. Nothing to do."
), nwAlert.ERROR)
return False
srcItem = self.theProject.projTree[self.sourceItem]
if srcItem is None:
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
return False
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
newItem = self.theProject.projTree[nHandle]
newItem.setStatus(srcItem.itemStatus)
outDoc = NWDoc(self.theProject, nHandle)
if not outDoc.writeDocument(theText):
self.theParent.makeAlert([
self.tr("Could not save document."), outDoc.getError()
], nwAlert.ERROR)
return False
self.theParent.treeView.revealNewTreeItem(nHandle)
self.theParent.openDocument(nHandle, doScroll=True)
self._doClose()
return True
def _doClose(self):
"""Close the dialog window without doing anything.
"""
self.close()
return
##
# Internal Functions
##
def _populateList(self):
"""Get the item selected in the tree, check that it is a folder,
and try to find all files associated with it. The valid files
are then added to the list view in order. The list itself can be
reordered by the user.
"""
tHandle = self.theParent.treeView.getSelectedHandle()
self.sourceItem = tHandle
if tHandle is None:
return False
nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
return False
if nwItem.itemType is not nwItemType.FOLDER:
self.theParent.makeAlert(self.tr(
"Element selected in the project tree must be a folder."
), nwAlert.ERROR)
return False
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
newItem = QListWidgetItem()
nwItem = self.theProject.projTree[sHandle]
if nwItem.itemType is not nwItemType.FILE:
continue
newItem.setText(nwItem.itemName)
newItem.setData(Qt.UserRole, sHandle)
self.listBox.addItem(newItem)
return True
# END Class GuiDocMerge
+300
View File
@@ -0,0 +1,300 @@
"""
novelWriter GUI Doc Split Tool
================================
GUI class for splitting a single document into multiple documents
File History:
Created: 2020-02-01 [0.4.3]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import novelwriter
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QComboBox, QListWidget, QAbstractItemView,
QListWidgetItem, QDialogButtonBox, QLabel
)
from novelwriter.core import NWDoc
from novelwriter.enum import nwAlert, nwItemType, nwItemClass, nwItemLayout
from novelwriter.constants import nwConst
from novelwriter.gui.custom import QHelpLabel
logger = logging.getLogger(__name__)
class GuiDocSplit(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiDocSplit ...")
self.setObjectName("GuiDocSplit")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.optState = theParent.theProject.optState
self.sourceItem = None
self.sourceText = []
self.outerBox = QVBoxLayout()
self.setWindowTitle(self.tr("Split Document"))
self.headLabel = QLabel("<b>%s</b>" % self.tr("Document Headers"))
self.helpLabel = QHelpLabel(
self.tr("Select the maximum level to split into files."),
self.theParent.theTheme.helpText
)
self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
self.splitLevel = QComboBox(self)
self.splitLevel.addItem(self.tr("Split on Header Level 1 (Title)"), 1)
self.splitLevel.addItem(self.tr("Split up to Header Level 2 (Chapter)"), 2)
self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3)
self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
spIndex = self.splitLevel.findData(
self.optState.getInt("GuiDocSplit", "spLevel", 3)
)
if spIndex != -1:
self.splitLevel.setCurrentIndex(spIndex)
self.splitLevel.currentIndexChanged.connect(self._populateList)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSplit)
self.buttonBox.rejected.connect(self._doClose)
self.outerBox.setSpacing(0)
self.outerBox.addWidget(self.headLabel)
self.outerBox.addWidget(self.helpLabel)
self.outerBox.addSpacing(self.mainConf.pxInt(8))
self.outerBox.addWidget(self.listBox)
self.outerBox.addWidget(self.splitLevel)
self.outerBox.addSpacing(self.mainConf.pxInt(12))
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
self.rejected.connect(self._doClose)
self._populateList()
logger.debug("GuiDocSplit initialisation complete")
return
##
# Buttons
##
def _doSplit(self):
"""Perform the split of the file, create a new folder in the
same parent folder, and multiple files depending on split level
settings. The old file is not removed in the split process, and
must be deleted manually.
"""
logger.verbose("GuiDocSplit split button clicked")
if self.sourceItem is None:
self.theParent.makeAlert(self.tr(
"No source document selected. Nothing to do."
), nwAlert.ERROR)
return False
srcItem = self.theProject.projTree[self.sourceItem]
if srcItem is None:
self.theParent.makeAlert(self.tr(
"Could not parse source document."
), nwAlert.ERROR)
return False
inDoc = NWDoc(self.theProject, self.sourceItem)
theText = inDoc.readDocument()
docErr = inDoc.getError()
if theText is None and docErr:
self.theParent.makeAlert([
self.tr("Failed to open document file."), docErr
], nwAlert.ERROR)
if theText is None:
theText = ""
nLines = len(self.sourceText)
logger.debug(
"Splitting document %s with %d lines" % (self.sourceItem, nLines)
)
finalOrder = []
for i in range(self.listBox.count()):
listItem = self.listBox.item(i)
wTitle = listItem.text()
lineNo = listItem.data(Qt.UserRole)
finalOrder.append([wTitle, lineNo, nLines])
if i > 0:
finalOrder[i-1][2] = lineNo
nFiles = len(finalOrder)
if nFiles == 0:
self.theParent.makeAlert(self.tr(
"No headers found. Nothing to do."
), nwAlert.ERROR)
return False
# Check that another folder can be created
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
if len(parTree) >= nwConst.MAX_DEPTH - 1:
self.theParent.makeAlert(self.tr(
"Cannot add new folder for the document split. "
"Maximum folder depth has been reached. "
"Please move the file to another level in the project tree."
), nwAlert.ERROR)
return False
msgYes = self.theParent.askQuestion(
self.tr("Split Document"),
"%s<br><br>%s" % (
self.tr(
"The document will be split into {0} file(s) in a new folder. "
"The original document will remain intact.").format(nFiles),
self.tr(
"Continue with the splitting process?"
)
)
)
if not msgYes:
return False
# Create the folder
fHandle = self.theProject.newFolder(
srcItem.itemName, srcItem.itemClass, srcItem.itemParent
)
self.theParent.treeView.revealNewTreeItem(fHandle)
logger.verbose("Creating folder '%s'", fHandle)
# Loop through, and create the files
for wTitle, iStart, iEnd in finalOrder:
if srcItem.itemClass == nwItemClass.NOVEL:
itemLayout = nwItemLayout.DOCUMENT
else:
itemLayout = nwItemLayout.NOTE
wTitle = wTitle.lstrip("#")
wTitle = wTitle.strip()
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
newItem = self.theProject.projTree[nHandle]
newItem.setLayout(itemLayout)
newItem.setStatus(srcItem.itemStatus)
logger.verbose(
"Creating new document %s with text from line %d to %d" % (
nHandle, iStart+1, iEnd
)
)
theText = "\n".join(self.sourceText[iStart:iEnd])
theText = theText.rstrip("\n") + "\n\n"
outDoc = NWDoc(self.theProject, nHandle)
if not outDoc.writeDocument(theText):
self.theParent.makeAlert([
self.tr("Could not save document."), outDoc.getError()
], nwAlert.ERROR)
return False
self.theParent.treeView.revealNewTreeItem(nHandle)
self._doClose()
return True
def _doClose(self):
"""Close the dialog window without doing anything.
"""
self.optState.saveSettings()
self.close()
return
##
# Internal Functions
##
def _populateList(self):
"""Get the item selected in the tree, check that it is a folder,
and try to find all files associated with it. The valid files
are then added to the list view in order. The list itself can be
reordered by the user.
"""
self.listBox.clear()
if self.sourceItem is None:
self.sourceItem = self.theParent.treeView.getSelectedHandle()
if self.sourceItem is None:
return False
nwItem = self.theProject.projTree[self.sourceItem]
if nwItem is None:
return False
if nwItem.itemType is not nwItemType.FILE:
self.theParent.makeAlert(self.tr(
"Element selected in the project tree must be a file."
), nwAlert.ERROR)
return False
inDoc = NWDoc(self.theProject, self.sourceItem)
theText = inDoc.readDocument()
if theText is None:
theText = ""
return False
spLevel = self.splitLevel.currentData()
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
logger.debug(
"Scanning document %s for headings level <= %d" % (self.sourceItem, spLevel)
)
self.sourceText = theText.splitlines()
for lineNo, aLine in enumerate(self.sourceText):
onLine = -1
if aLine.startswith("# ") and spLevel >= 1:
onLine = lineNo
elif aLine.startswith("## ") and spLevel >= 2:
onLine = lineNo
elif aLine.startswith("### ") and spLevel >= 3:
onLine = lineNo
elif aLine.startswith("#### ") and spLevel >= 4:
onLine = lineNo
if onLine >= 0:
newItem = QListWidgetItem()
newItem.setText(aLine.strip())
newItem.setData(Qt.UserRole, onLine)
self.listBox.addItem(newItem)
return True
# END Class GuiDocSplit
+201
View File
@@ -0,0 +1,201 @@
"""
novelWriter GUI Item Editor
=============================
GUI class for the item editor dialog
File History:
Created: 2019-04-27 [0.0.1]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import novelwriter
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
QDialogButtonBox
)
from novelwriter.enum import nwItemLayout, nwItemType
from novelwriter.constants import trConst, nwLists, nwLabels
from novelwriter.gui.custom import QSwitch
logger = logging.getLogger(__name__)
class GuiItemEditor(QDialog):
def __init__(self, theParent, tHandle):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiItemEditor ...")
self.setObjectName("GuiItemEditor")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
##
# Build GUI
##
self.theItem = self.theProject.projTree[tHandle]
if self.theItem is None:
self._doClose()
self.setWindowTitle(self.tr("Item Settings"))
mVd = self.mainConf.pxInt(220)
mSp = self.mainConf.pxInt(16)
vSp = self.mainConf.pxInt(4)
# Item Label
self.editName = QLineEdit()
self.editName.setMinimumWidth(mVd)
self.editName.setMaxLength(200)
# Item Status
self.editStatus = QComboBox()
self.editStatus.setMinimumWidth(mVd)
if self.theItem.itemClass in nwLists.CLS_NOVEL:
for sLabel, _, _ in self.theProject.statusItems:
self.editStatus.addItem(
self.theParent.statusIcons[sLabel], sLabel, sLabel
)
else:
for sLabel, _, _ in self.theProject.importItems:
self.editStatus.addItem(
self.theParent.importIcons[sLabel], sLabel, sLabel
)
# Item Layout
self.editLayout = QComboBox()
self.editLayout.setMinimumWidth(mVd)
validLayouts = []
if self.theItem.itemType == nwItemType.FILE:
if self.theItem.itemClass in nwLists.CLS_NOVEL:
validLayouts.append(nwItemLayout.DOCUMENT)
validLayouts.append(nwItemLayout.NOTE)
else:
validLayouts.append(nwItemLayout.NO_LAYOUT)
self.editLayout.setEnabled(False)
for itemLayout in nwItemLayout:
if itemLayout in validLayouts:
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
# Export Switch
self.textExport = QLabel(self.tr("Include when building project"))
self.editExport = QSwitch()
if self.theItem.itemType == nwItemType.FILE:
self.editExport.setEnabled(True)
self.editExport.setChecked(self.theItem.isExported)
else:
self.editExport.setEnabled(False)
self.editExport.setChecked(False)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
# Set Current Values
self.editName.setText(self.theItem.itemName)
self.editName.selectAll()
statusIdx = self.editStatus.findData(self.theItem.itemStatus)
if statusIdx != -1:
self.editStatus.setCurrentIndex(statusIdx)
layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
if layoutIdx != -1:
self.editLayout.setCurrentIndex(layoutIdx)
##
# Assemble
##
nameLabel = QLabel(self.tr("Label"))
statusLabel = QLabel(self.tr("Status"))
layoutLabel = QLabel(self.tr("Layout"))
self.mainForm = QGridLayout()
self.mainForm.setVerticalSpacing(vSp)
self.mainForm.setHorizontalSpacing(mSp)
self.mainForm.addWidget(nameLabel, 0, 0, 1, 1)
self.mainForm.addWidget(self.editName, 0, 1, 1, 2)
self.mainForm.addWidget(statusLabel, 1, 0, 1, 1)
self.mainForm.addWidget(self.editStatus, 1, 1, 1, 2)
self.mainForm.addWidget(layoutLabel, 2, 0, 1, 1)
self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2)
self.mainForm.addWidget(self.textExport, 3, 0, 1, 2)
self.mainForm.addWidget(self.editExport, 3, 2, 1, 1)
self.mainForm.setColumnStretch(0, 0)
self.mainForm.setColumnStretch(1, 1)
self.mainForm.setColumnStretch(2, 0)
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(mSp)
self.outerBox.addLayout(self.mainForm)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
self.rejected.connect(self._doClose)
logger.debug("GuiItemEditor initialisation complete")
return
##
# Slots
##
@pyqtSlot()
def _doSave(self):
"""Save the setting to the item.
"""
logger.verbose("ItemEditor save button clicked")
itemName = self.editName.text()
itemStatus = self.editStatus.currentData()
itemLayout = self.editLayout.currentData()
isExported = self.editExport.isChecked()
self.theItem.setName(itemName)
self.theItem.setStatus(itemStatus)
self.theItem.setLayout(itemLayout)
self.theItem.setExported(isExported)
self.theProject.setProjectChanged(True)
self.accept()
self.close()
return
@pyqtSlot()
def _doClose(self):
"""Close the dialog without saving the settings.
"""
logger.verbose("ItemEditor cancel button clicked")
self.close()
return
# END Class GuiItemEditor
File diff suppressed because it is too large Load Diff
+305
View File
@@ -0,0 +1,305 @@
"""
novelWriter GUI Open Project
==============================
GUI class for the load/browse/new project dialog
File History:
Created: 2020-02-26 [0.4.5]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import logging
import novelwriter
from datetime import datetime
from PyQt5.QtGui import QKeySequence
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton, QTreeWidget,
QAbstractItemView, QTreeWidgetItem, QDialogButtonBox, QLabel, QShortcut,
QFileDialog, QLineEdit
)
from novelwriter.common import formatInt
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
class GuiProjectLoad(QDialog):
NONE_STATE = 0
NEW_STATE = 1
OPEN_STATE = 2
C_NAME = 0
C_COUNT = 1
C_TIME = 2
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiProjectLoad ...")
self.setObjectName("GuiProjectLoad")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.openState = self.NONE_STATE
self.openPath = None
sPx = self.mainConf.pxInt(16)
nPx = self.mainConf.pxInt(96)
iPx = self.theTheme.baseIconSize
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.outerBox.setSpacing(sPx)
self.innerBox.setSpacing(sPx)
self.setWindowTitle(self.tr("Open Project"))
self.setMinimumWidth(self.mainConf.pxInt(650))
self.setMinimumHeight(self.mainConf.pxInt(400))
self.setModal(True)
self.nwIcon = QLabel()
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
self.innerBox.addWidget(self.nwIcon, 0, Qt.AlignTop)
self.projectForm = QGridLayout()
self.projectForm.setContentsMargins(0, 0, 0, 0)
self.listBox = QTreeWidget()
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
self.listBox.setColumnCount(3)
self.listBox.setHeaderLabels([
self.tr("Working Title"),
self.tr("Words"),
self.tr("Last Opened"),
])
self.listBox.setRootIsDecorated(False)
self.listBox.itemSelectionChanged.connect(self._doSelectRecent)
self.listBox.itemDoubleClicked.connect(self._doOpenRecent)
self.listBox.setIconSize(QSize(iPx, iPx))
treeHead = self.listBox.headerItem()
treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight)
treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight)
self.lblRecent = QLabel("<b>%s</b>" % self.tr("Recently Opened Projects"))
self.lblPath = QLabel("<b>%s</b>" % self.tr("Path"))
self.selPath = QLineEdit("")
self.selPath.setReadOnly(True)
self.browseButton = QPushButton("...")
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
self.browseButton.clicked.connect(self._doBrowse)
self.projectForm.addWidget(self.lblRecent, 0, 0, 1, 3)
self.projectForm.addWidget(self.listBox, 1, 0, 1, 3)
self.projectForm.addWidget(self.lblPath, 2, 0, 1, 1)
self.projectForm.addWidget(self.selPath, 2, 1, 1, 1)
self.projectForm.addWidget(self.browseButton, 2, 2, 1, 1)
self.projectForm.setColumnStretch(0, 0)
self.projectForm.setColumnStretch(1, 1)
self.projectForm.setColumnStretch(2, 0)
self.projectForm.setVerticalSpacing(self.mainConf.pxInt(4))
self.projectForm.setHorizontalSpacing(self.mainConf.pxInt(8))
self.innerBox.addLayout(self.projectForm)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doOpenRecent)
self.buttonBox.rejected.connect(self._doCancel)
self.newButton = self.buttonBox.addButton(self.tr("New"), QDialogButtonBox.ActionRole)
self.newButton.clicked.connect(self._doNewProject)
self.delButton = self.buttonBox.addButton(self.tr("Remove"), QDialogButtonBox.ActionRole)
self.delButton.clicked.connect(self._doDeleteRecent)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
self._populateList()
self._doSelectRecent()
keyDelete = QShortcut(self.listBox)
keyDelete.setKey(QKeySequence(Qt.Key_Delete))
keyDelete.activated.connect(self._doDeleteRecent)
logger.debug("GuiProjectLoad initialisation complete")
return
##
# Buttons
##
def _doOpenRecent(self):
"""Close the dialog window with a recent project selected.
"""
logger.verbose("GuiProjectLoad open button clicked")
self._saveSettings()
self.openPath = None
self.openState = self.NONE_STATE
selItems = self.listBox.selectedItems()
if selItems:
self.openPath = selItems[0].data(self.C_NAME, Qt.UserRole)
self.openState = self.OPEN_STATE
self.accept()
return
def _doSelectRecent(self):
"""A recent item has been selected.
"""
selList = self.listBox.selectedItems()
if selList:
self.selPath.setText(selList[0].data(self.C_NAME, Qt.UserRole))
return
def _doBrowse(self):
"""Browse for a folder path.
"""
logger.verbose("GuiProjectLoad browse button clicked")
extFilter = [
self.tr("novelWriter Project File ({0})").format(nwFiles.PROJ_FILE),
self.tr("All files ({0})").format("*"),
]
projFile, _ = QFileDialog.getOpenFileName(
self, self.tr("Open Project"), "", filter=";;".join(extFilter)
)
if projFile:
thePath = os.path.abspath(os.path.dirname(projFile))
self.selPath.setText(thePath)
self.openPath = thePath
self.openState = self.OPEN_STATE
self.accept()
return
def _doCancel(self):
"""Close the dialog window without doing anything.
"""
logger.verbose("GuiProjectLoad close button clicked")
self.openPath = None
self.openState = self.NONE_STATE
self.close()
return
def _doNewProject(self):
"""Create a new project.
"""
logger.verbose("GuiProjectLoad new project button clicked")
self._saveSettings()
self.openPath = None
self.openState = self.NEW_STATE
self.accept()
return
def _doDeleteRecent(self):
"""Remove an entry from the recent projects list.
"""
selList = self.listBox.selectedItems()
if selList:
projName = selList[0].text(self.C_NAME)
msgYes = self.theParent.askQuestion(
self.tr("Remove Entry"),
self.tr(
"Remove '{0}' from the recent projects list? "
"The project files will not be deleted."
).format(projName)
)
if msgYes:
self.mainConf.removeFromRecentCache(
selList[0].data(self.C_NAME, Qt.UserRole)
)
self._populateList()
return
##
# Events
##
def closeEvent(self, theEvent):
"""Capture the user closing the dialog so we can save settings.
"""
self._saveSettings()
theEvent.accept()
return
##
# Internal Functions
##
def _saveSettings(self):
"""Save the changes made to the dialog.
"""
colWidths = [0, 0, 0]
colWidths[self.C_NAME] = self.listBox.columnWidth(self.C_NAME)
colWidths[self.C_COUNT] = self.listBox.columnWidth(self.C_COUNT)
colWidths[self.C_TIME] = self.listBox.columnWidth(self.C_TIME)
self.mainConf.setProjColWidths(colWidths)
return
def _populateList(self):
"""Populate the list box with recent project data.
"""
dataList = []
for projPath in self.mainConf.recentProj:
theEntry = self.mainConf.recentProj[projPath]
theTitle = theEntry.get("title", "")
theTime = theEntry.get("time", 0)
theWords = theEntry.get("words", 0)
dataList.append([theTitle, theTime, theWords, projPath])
self.listBox.clear()
sortList = sorted(dataList, key=lambda x: x[1], reverse=True)
for theTitle, theTime, theWords, projPath in sortList:
newItem = QTreeWidgetItem([""]*4)
newItem.setIcon(self.C_NAME, self.theParent.theTheme.getIcon("proj_nwx"))
newItem.setText(self.C_NAME, theTitle)
newItem.setData(self.C_NAME, Qt.UserRole, projPath)
newItem.setText(self.C_COUNT, formatInt(theWords))
newItem.setText(self.C_TIME, datetime.fromtimestamp(theTime).strftime("%x %X"))
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
self.listBox.addTopLevelItem(newItem)
if self.listBox.topLevelItemCount() > 0:
self.listBox.topLevelItem(0).setSelected(True)
projColWidth = self.mainConf.getProjColWidths()
if len(projColWidth) == 3:
self.listBox.setColumnWidth(self.C_NAME, projColWidth[self.C_NAME])
self.listBox.setColumnWidth(self.C_COUNT, projColWidth[self.C_COUNT])
self.listBox.setColumnWidth(self.C_TIME, projColWidth[self.C_TIME])
return
# END Class GuiProjectLoad
+681
View File
@@ -0,0 +1,681 @@
"""
novelWriter GUI Project Settings
==================================
GUI classes for the project settings dialog
File History:
Created: 2018-09-29 [0.0.1]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import novelwriter
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
from PyQt5.QtCore import Qt, QLocale
from PyQt5.QtWidgets import (
QHBoxLayout, QVBoxLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget,
QDialogButtonBox, QPushButton, QColorDialog, QTreeWidget, QTreeWidgetItem,
QComboBox
)
from novelwriter.enum import nwAlert
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
logger = logging.getLogger(__name__)
class GuiProjectSettings(PagedDialog):
def __init__(self, theParent):
PagedDialog.__init__(self, theParent)
logger.debug("Initialising GuiProjectSettings ...")
self.setObjectName("GuiProjectSettings")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.optState = theParent.theProject.optState
self.theProject.countStatus()
self.setWindowTitle(self.tr("Project Settings"))
wW = self.mainConf.pxInt(570)
wH = self.mainConf.pxInt(375)
self.setMinimumWidth(wW)
self.setMinimumHeight(wH)
self.resize(
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winWidth", wW)),
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winHeight", wH))
)
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
self.tabStatus = GuiProjectEditStatus(self.theParent, self.theProject, True)
self.tabImport = GuiProjectEditStatus(self.theParent, self.theProject, False)
self.tabReplace = GuiProjectEditReplace(self.theParent, self.theProject)
self.addTab(self.tabMain, self.tr("Settings"))
self.addTab(self.tabStatus, self.tr("Status"))
self.addTab(self.tabImport, self.tr("Importance"))
self.addTab(self.tabReplace, self.tr("Auto-Replace"))
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox)
logger.debug("GuiProjectSettings initialisation complete")
return
##
# Slots
##
def _doSave(self):
"""Save settings and close dialog.
"""
logger.verbose("GuiProjectSettings save button clicked")
projName = self.tabMain.editName.text()
bookTitle = self.tabMain.editTitle.text()
bookAuthors = self.tabMain.editAuthors.toPlainText()
spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked()
self.theProject.setProjectName(projName)
self.theProject.setBookTitle(bookTitle)
self.theProject.setBookAuthors(bookAuthors)
self.theProject.setSpellLang(spellLang)
self.theProject.setProjBackup(doBackup)
if self.tabStatus.colChanged:
statusCol = self.tabStatus.getNewList()
self.theProject.setStatusColours(statusCol)
if self.tabImport.colChanged:
importCol = self.tabImport.getNewList()
self.theProject.setImportColours(importCol)
if self.tabStatus.colChanged or self.tabImport.colChanged:
self.theParent.rebuildTrees()
if self.tabReplace.arChanged:
newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList)
self._saveGuiSettings()
self.accept()
return
def _doClose(self):
"""Save settings and close the dialog.
"""
self._saveGuiSettings()
self.reject()
return
##
# Internal Functions
##
def _saveGuiSettings(self):
"""Save GUI settings.
"""
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
replaceColW = self.mainConf.rpxInt(self.tabReplace.listBox.columnWidth(0))
statusColW = self.mainConf.rpxInt(self.tabStatus.listBox.columnWidth(0))
importColW = self.mainConf.rpxInt(self.tabImport.listBox.columnWidth(0))
self.optState.setValue("GuiProjectSettings", "winWidth", winWidth)
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
self.optState.setValue("GuiProjectSettings", "statusColW", statusColW)
self.optState.setValue("GuiProjectSettings", "importColW", importColW)
return
# END Class GuiProjectSettings
class GuiProjectEditMain(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theProject
# The Form
self.mainForm = QConfigLayout()
self.mainForm.setHelpTextStyle(self.theParent.theTheme.helpText)
self.setLayout(self.mainForm)
self.mainForm.addGroupLabel(self.tr("Project Settings"))
xW = self.mainConf.pxInt(250)
xH = round(4.8*self.theParent.theTheme.fontPixelSize)
self.editName = QLineEdit()
self.editName.setMaxLength(200)
self.editName.setMaximumWidth(xW)
self.editName.setText(self.theProject.projName)
self.mainForm.addRow(
self.tr("Working title"),
self.editName,
self.tr("Should be set only once.")
)
self.editTitle = QLineEdit()
self.editTitle.setMaxLength(200)
self.editTitle.setMaximumWidth(xW)
self.editTitle.setText(self.theProject.bookTitle)
self.mainForm.addRow(
self.tr("Novel title"),
self.editTitle,
self.tr("Change whenever you want!")
)
self.editAuthors = QPlainTextEdit()
self.editAuthors.setMaximumHeight(xH)
self.editAuthors.setMaximumWidth(xW)
self.editAuthors.setPlainText("\n".join(self.theProject.bookAuthors))
self.mainForm.addRow(
self.tr("Author(s)"),
self.editAuthors,
self.tr("One name per line.")
)
self.spellLang = QComboBox(self)
self.spellLang.setMaximumWidth(xW)
theDict = self.theParent.docEditor.currentDictionary()
self.spellLang.addItem(self.tr("Default"), "None")
if theDict is not None:
for spTag, spProv in theDict.listDictionaries():
qLocal = QLocale(spTag)
spLang = qLocal.nativeLanguageName().title()
self.spellLang.addItem("%s [%s]" % (spLang, spProv), spTag)
self.mainForm.addRow(
self.tr("Spell check language"),
self.spellLang,
self.tr("Overrides main preferences.")
)
spellIdx = 0
if self.theProject.projSpell is not None:
spellIdx = self.spellLang.findData(self.theProject.projSpell)
if spellIdx != -1:
self.spellLang.setCurrentIndex(spellIdx)
self.doBackup = QSwitch(self)
self.doBackup.setChecked(not self.theProject.doBackup)
self.mainForm.addRow(
self.tr("No backup on close"),
self.doBackup,
self.tr("Overrides main preferences.")
)
return
# END Class GuiProjectEditMain
class GuiProjectEditStatus(QWidget):
COL_LABEL = 0
COL_USAGE = 1
def __init__(self, theParent, theProject, isStatus):
QWidget.__init__(self, theParent)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theProject
self.optState = theProject.optState
self.theTheme = theParent.theTheme
if isStatus:
self.theStatus = self.theProject.statusItems
pageLabel = self.tr("Novel File Status Levels")
colSetting = "statusColW"
else:
self.theStatus = self.theProject.importItems
pageLabel = self.tr("Note File Importance Levels")
colSetting = "importColW"
wCol0 = self.mainConf.pxInt(
self.optState.getInt("GuiProjectSettings", colSetting, 130)
)
self.colData = []
self.colCounts = []
self.colChanged = False
self.selColour = None
self.iPx = self.theTheme.baseIconSize
# The List
# ========
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels([
self.tr("Label"),
self.tr("Usage"),
])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setIndentation(0)
for iName, iCol, nUse in self.theStatus:
self._addItem(iName, iCol, iName, nUse)
# List Controls
# =============
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
self.addButton.setToolTip(self.tr("Add new entry"))
self.addButton.clicked.connect(self._newItem)
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
self.delButton.setToolTip(self.tr("Delete selected entry"))
self.delButton.clicked.connect(self._delItem)
# Edit Form
# =========
self.editName = QLineEdit()
self.editName.setMaxLength(40)
self.editName.setEnabled(False)
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.colPixmap = QPixmap(self.iPx, self.iPx)
self.colPixmap.fill(QColor(120, 120, 120))
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
self.colButton.setIconSize(self.colPixmap.rect().size())
self.colButton.clicked.connect(self._selectColour)
self.saveButton = QPushButton(self.tr("Save"))
self.saveButton.clicked.connect(self._saveItem)
# Assemble
# ========
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton)
self.listControls.addStretch(1)
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editName)
self.editBox.addWidget(self.colButton)
self.editBox.addWidget(self.saveButton)
self.mainBox = QVBoxLayout()
self.mainBox.addWidget(self.listBox)
self.mainBox.addLayout(self.editBox)
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox)
self.innerBox.addLayout(self.listControls)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox)
return
def getNewList(self):
"""Return list of entries.
"""
if self.colChanged:
newList = []
for n in range(self.listBox.topLevelItemCount()):
nItem = self.listBox.topLevelItem(n)
nIdx = nItem.data(self.COL_LABEL, Qt.UserRole)
newList.append(self.colData[nIdx])
return newList
return None
##
# User Actions
##
def _selectColour(self):
"""Open a dialog to select the status icon colour.
"""
if self.selColour is not None:
newCol = QColorDialog.getColor(
self.selColour, self, self.tr("Select Colour")
)
if newCol.isValid():
self.selColour = newCol
colPixmap = QPixmap(self.iPx, self.iPx)
colPixmap.fill(newCol)
self.colButton.setIcon(QIcon(colPixmap))
self.colButton.setIconSize(colPixmap.rect().size())
return
def _newItem(self):
"""Create a new status item.
"""
newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0)
newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70)))
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
self.colChanged = True
return
def _delItem(self):
"""Delete a status item.
"""
selItem = self._getSelectedItem()
if selItem is not None:
iRow = self.listBox.indexOfTopLevelItem(selItem)
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
if self.colCounts[selIdx] == 0:
self.listBox.takeTopLevelItem(iRow)
self.colChanged = True
else:
self.theParent.makeAlert(self.tr(
"Cannot delete a status item that is in use."
), nwAlert.ERROR)
return
def _saveItem(self):
"""Save changes made to a status item.
"""
selItem = self._getSelectedItem()
if selItem is not None:
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
self.colData[selIdx] = (
self.editName.text().strip(),
self.selColour.red(),
self.selColour.green(),
self.selColour.blue(),
self.colData[selIdx][4]
)
selItem.setText(self.COL_LABEL, self.colData[selIdx][0])
selItem.setText(self.COL_USAGE, self._usageString(self.colCounts[selIdx]))
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
self.editName.setEnabled(False)
self.colChanged = True
return
def _addItem(self, iName, iCol, oName, nUse):
"""Add a status item to the list.
"""
newIcon = QPixmap(self.iPx, self.iPx)
newIcon.fill(QColor(*iCol))
newItem = QTreeWidgetItem()
newItem.setText(self.COL_LABEL, iName)
newItem.setText(self.COL_USAGE, self._usageString(nUse))
newItem.setIcon(self.COL_LABEL, QIcon(newIcon))
newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData))
self.listBox.addTopLevelItem(newItem)
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName))
self.colCounts.append(nUse)
return newItem
def _selectedItem(self):
"""Extract the info of a selected item and populate the settings
boxes and button.
"""
selItem = self._getSelectedItem()
if selItem is not None:
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
selVal = self.colData[selIdx]
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
newIcon = QPixmap(self.iPx, self.iPx)
newIcon.fill(self.selColour)
self.editName.setText(selVal[0])
self.colButton.setIcon(QIcon(newIcon))
self.editName.setEnabled(True)
self.editName.selectAll()
self.editName.setFocus()
return
##
# Internal Functions
##
def _getSelectedItem(self):
"""Get the currently selected item.
"""
selItem = self.listBox.selectedItems()
if len(selItem) > 0:
return selItem[0]
return None
def _rowsMoved(self):
"""A row has been moved, so set the changed flag.
"""
self.colChanged = True
return
def _usageString(self, nUse):
"""Generate usage string.
"""
if nUse == 0:
return self.tr("Not in use")
elif nUse == 1:
return self.tr("Used once")
else:
return self.tr("Used by {0} items").format(nUse)
# END Class GuiProjectEditStatus
class GuiProjectEditReplace(QWidget):
COL_KEY = 0
COL_REPL = 1
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theProject
self.optState = theProject.optState
self.arChanged = False
wCol0 = self.mainConf.pxInt(
self.optState.getInt("GuiProjectSettings", "replaceColW", 130)
)
pageLabel = self.tr("Text Replace List for Preview and Export")
# List Box
# ========
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels([
self.tr("Keyword"),
self.tr("Replace With"),
])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
self.listBox.setIndentation(0)
for aKey, aVal in self.theProject.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem)
self.listBox.sortByColumn(self.COL_KEY, Qt.AscendingOrder)
self.listBox.setSortingEnabled(True)
# List Controls
# =============
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
self.addButton.setToolTip(self.tr("Add new entry"))
self.addButton.clicked.connect(self._addEntry)
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
self.delButton.setToolTip(self.tr("Delete selected entry"))
self.delButton.clicked.connect(self._delEntry)
# Edit Form
# =========
self.editKey = QLineEdit()
self.editKey.setPlaceholderText(self.tr("Select item to edit"))
self.editKey.setEnabled(False)
self.editKey.setMaxLength(40)
self.editValue = QLineEdit()
self.editValue.setEnabled(False)
self.editValue.setMaxLength(80)
self.saveButton = QPushButton("Save")
self.saveButton.setToolTip(self.tr("Save entry"))
self.saveButton.clicked.connect(self._saveEntry)
# Assemble
# ========
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton)
self.listControls.addStretch(1)
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editKey, 4)
self.editBox.addWidget(self.editValue, 5)
self.editBox.addWidget(self.saveButton, 0)
self.mainBox = QVBoxLayout()
self.mainBox.addWidget(self.listBox)
self.mainBox.addLayout(self.editBox)
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox)
self.innerBox.addLayout(self.listControls)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox)
return
def getNewList(self):
"""Extract the list from the widget.
"""
newList = {}
for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n)
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
newList[aKey] = aVal
return newList
##
# Internal Functions
##
def _selectedItem(self):
"""Extract the details from the selected item and populate the
edit form.
"""
selItem = self._getSelectedItem()
if selItem is None:
return False
editKey = self._stripNotAllowed(selItem.text(0))
editVal = selItem.text(1)
self.editKey.setText(editKey)
self.editValue.setText(editVal)
self.editKey.setEnabled(True)
self.editValue.setEnabled(True)
self.editKey.selectAll()
self.editKey.setFocus()
return True
def _saveEntry(self):
"""Save the form data into the list widget.
"""
selItem = self._getSelectedItem()
if selItem is None:
return False
newKey = self.editKey.text()
newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0:
selItem.setText(self.COL_KEY, "<%s>" % saveKey)
selItem.setText(self.COL_REPL, newVal)
self.editKey.clear()
self.editValue.clear()
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self.arChanged = True
return
def _addEntry(self):
"""Add a new list entry.
"""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
newVal = ""
newItem = QTreeWidgetItem([saveKey, newVal])
self.listBox.addTopLevelItem(newItem)
return True
def _delEntry(self):
"""Delete the selected entry.
"""
selItem = self._getSelectedItem()
if selItem is None:
return False
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.arChanged = True
return True
def _getSelectedItem(self):
"""Extract the currently selected item.
"""
selItem = self.listBox.selectedItems()
if len(selItem) == 0:
return None
return selItem[0]
def _stripNotAllowed(self, theKey):
"""Clean up the replace key string.
"""
retKey = ""
for c in theKey:
if c.isalnum():
retKey += c
return retKey
# END Class GuiProjectEditReplace
+133
View File
@@ -0,0 +1,133 @@
"""
novelWriter GUI Quotes Dialog
===============================
GUI class for quotes dialog
File History:
Created: 2020-06-18 [0.9]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import novelwriter
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (
QLabel, QVBoxLayout, QHBoxLayout, QDialog, QDialogButtonBox,
QListWidget, QListWidgetItem, QFrame
)
from novelwriter.constants import trConst, nwQuotes
logger = logging.getLogger(__name__)
class GuiQuoteSelect(QDialog):
selectedQuote = ""
def __init__(self, theParent=None, currentQuote='"'):
QDialog.__init__(self, parent=theParent)
self.mainConf = novelwriter.CONFIG
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout()
self.selectedQuote = currentQuote
qMetrics = QFontMetrics(self.font())
pxW = 7*qMetrics.boundingRectChar("M").width()
pxH = 7*qMetrics.boundingRectChar("M").height()
pxH = 7*qMetrics.boundingRectChar("M").height()
lblFont = self.font()
lblFont.setPointSizeF(4*lblFont.pointSizeF())
# Preview Label
self.previewLabel = QLabel(currentQuote)
self.previewLabel.setFont(lblFont)
self.previewLabel.setFixedSize(QSize(pxW, pxH))
self.previewLabel.setAlignment(Qt.AlignCenter)
self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
# Quote Symbols
self.listBox = QListWidget()
self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
minSize = 100
for sKey, sLabel in nwQuotes.SYMBOLS.items():
theText = "[ %s ] %s" % (sKey, trConst(sLabel))
minSize = max(minSize, qMetrics.boundingRect(theText).width())
qtItem = QListWidgetItem(theText)
qtItem.setData(Qt.UserRole, sKey)
self.listBox.addItem(qtItem)
if sKey == currentQuote:
self.listBox.setCurrentItem(qtItem)
self.listBox.setMinimumWidth(minSize + self.mainConf.pxInt(40))
self.listBox.setMinimumHeight(self.mainConf.pxInt(150))
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doAccept)
self.buttonBox.rejected.connect(self._doReject)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
self.labelBox.addStretch(1)
self.innerBox.addLayout(self.labelBox)
self.innerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
return
##
# Slots
##
def _selectedSymbol(self):
"""Update the preview label and the selected quote style.
"""
selItems = self.listBox.selectedItems()
if selItems:
theSymbol = selItems[0].data(Qt.UserRole)
self.previewLabel.setText(theSymbol)
self.selectedQuote = theSymbol
return
def _doAccept(self):
"""Ok button clicked.
"""
self.accept()
return
def _doReject(self):
"""Cancel button clicked.
"""
self.reject()
return
# END Class GuiQuoteSelect
+170
View File
@@ -0,0 +1,170 @@
"""
novelWriter GUI Updates
=========================
A dialog box for checking for latest updates
File History:
Created: 2021-08-21 [1.5-alpah0]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import json
import logging
import novelwriter
from datetime import datetime
from urllib.request import Request, urlopen
from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QLabel
)
from novelwriter.common import logException
logger = logging.getLogger(__name__)
class GuiUpdates(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiUpdates ...")
self.setObjectName("GuiUpdates")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.setWindowTitle(self.tr("Check for Updates"))
nPx = self.mainConf.pxInt(96)
sPx = self.mainConf.pxInt(16)
tPx = self.mainConf.pxInt(8)
mPx = self.mainConf.pxInt(4)
# Left Box
self.nwIcon = QLabel()
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
self.leftBox = QVBoxLayout()
self.leftBox.addWidget(self.nwIcon)
self.leftBox.addStretch(1)
# Right Box
self.currentLabel = QLabel(self.tr("Current Release"))
self.currentValue = QLabel(self.tr(
"novelWriter {0} released on {1}"
).format(
"v%s" % novelwriter.__version__,
datetime.strptime(novelwriter.__date__, "%Y-%m-%d").strftime("%x"))
)
self.latestLabel = QLabel(self.tr("Latest Release"))
self.latestValue = QLabel(self.tr("Checking ..."))
self.latestLink = QLabel("")
self.latestLink.setOpenExternalLinks(True)
self.rightBox = QVBoxLayout()
self.rightBox.addWidget(self.currentLabel)
self.rightBox.addWidget(self.currentValue)
self.rightBox.addSpacing(tPx)
self.rightBox.addWidget(self.latestLabel)
self.rightBox.addWidget(self.latestValue)
self.rightBox.addSpacing(tPx)
self.rightBox.addWidget(self.latestLink)
self.rightBox.setSpacing(mPx)
hFont = self.currentLabel.font()
hFont.setBold(True)
self.currentLabel.setFont(hFont)
self.latestLabel.setFont(hFont)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
self.buttonBox.accepted.connect(self._doClose)
# Assemble
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.leftBox)
self.innerBox.addLayout(self.rightBox)
self.innerBox.setSpacing(sPx)
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.outerBox.setSpacing(sPx)
self.setLayout(self.outerBox)
logger.debug("GuiUpdates initialisation complete")
return
def checkLatest(self):
"""Check for latest release.
"""
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
urlReq = Request("https://api.github.com/repos/vkbo/novelwriter/releases/latest")
urlReq.add_header("User-Agent", "Mozilla/5.0 (compatible; novelWriter (Python))")
urlReq.add_header("Accept", "application/vnd.github.v3+json")
rawData = {}
try:
urlData = urlopen(urlReq, timeout=10)
rawData = json.loads(urlData.read().decode())
except Exception:
logger.error("Failed to contact GitHub API")
logException()
relVersion = rawData.get("tag_name", "Unknown")
relDate = rawData.get("created_at", None)
try:
relDate = datetime.strptime(relDate[:10], "%Y-%m-%d").strftime("%x")
except Exception:
relDate = "Unknown"
logException()
self.latestValue.setText(self.tr(
"novelWriter {0} released on {1}"
).format(
relVersion, relDate
))
self.latestLink.setText(self.tr(
"Download: {0}"
).format(
f'<a href="{novelwriter.__url__}">{novelwriter.__url__}</a>'
))
qApp.restoreOverrideCursor()
return
##
# Internal Functions
##
def _doClose(self):
self.close()
return
# END Class GuiUpdates
+216
View File
@@ -0,0 +1,216 @@
"""
novelWriter GUI User Wordlist
===============================
Class holding the user's wordlist dialog
File History:
Created: 2021-02-12 [1.2b1]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import logging
import novelwriter
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QVBoxLayout, QHBoxLayout, QListWidget,
QAbstractItemView, QPushButton, QLineEdit, QLabel
)
from novelwriter.enum import nwAlert
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
class GuiWordList(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiWordList ...")
self.setObjectName("GuiWordList")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theParent.theProject
self.optState = theParent.theProject.optState
self.setWindowTitle(self.tr("Project Word List"))
mS = self.mainConf.pxInt(250)
wW = self.mainConf.pxInt(320)
wH = self.mainConf.pxInt(340)
self.setMinimumWidth(mS)
self.setMinimumHeight(mS)
self.resize(
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winWidth", wW)),
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winHeight", wH))
)
# Main Widgets
# ============
self.headLabel = QLabel("<b>%s</b>" % self.tr("Project Word List"))
self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
self.listBox.setSortingEnabled(True)
self.newEntry = QLineEdit()
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
self.addButton.setToolTip(self.tr("Add new entry"))
self.addButton.clicked.connect(self._doAdd)
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
self.delButton.setToolTip(self.tr("Delete selected entry"))
self.delButton.clicked.connect(self._doDelete)
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.newEntry, 1)
self.editBox.addWidget(self.addButton, 0)
self.editBox.addWidget(self.delButton, 0)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
# Assemble
# ========
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.headLabel)
self.outerBox.addSpacing(self.mainConf.pxInt(8))
self.outerBox.addWidget(self.listBox, 1)
self.outerBox.addLayout(self.editBox, 0)
self.outerBox.addSpacing(self.mainConf.pxInt(12))
self.outerBox.addWidget(self.buttonBox, 0)
self.setLayout(self.outerBox)
self._loadWordList()
logger.debug("GuiWordList initialisation complete")
return
##
# Slots
##
def _doAdd(self):
"""Add a new word to the word list.
"""
newWord = self.newEntry.text().strip()
if newWord == "":
self.theParent.makeAlert(self.tr(
"Cannot add a blank word."
), nwAlert.ERROR)
return False
if self.listBox.findItems(newWord, Qt.MatchExactly):
self.theParent.makeAlert(self.tr(
"The word '{0}' is already in the word list."
).format(newWord), nwAlert.ERROR)
return False
self.listBox.addItem(newWord)
self.newEntry.setText("")
return True
def _doDelete(self):
"""Delete the selected item.
"""
selItem = self.listBox.selectedItems()
if selItem:
self.listBox.takeItem(self.listBox.row(selItem[0]))
return
def _doSave(self):
"""Save the new word list and close.
"""
self._saveGuiSettings()
dctFile = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT)
tmpFile = dctFile + "~"
try:
with open(tmpFile, mode="w", encoding="utf-8") as outFile:
for i in range(self.listBox.count()):
outFile.write(self.listBox.item(i).text() + "\n")
except Exception:
logger.error("Could not save new word list")
novelwriter.logException()
self.reject()
return False
if os.path.isfile(dctFile):
os.unlink(dctFile)
os.rename(tmpFile, dctFile)
self.accept()
return True
def _doClose(self):
"""Close without saving the word list.
"""
self._saveGuiSettings()
self.reject()
return
##
# Internal Functions
##
def _loadWordList(self):
"""Load the project's word list, if it exists.
"""
self.listBox.clear()
wordList = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT)
if not os.path.isfile(wordList):
logger.debug("No project dictionary file found")
return False
with open(wordList, mode="r", encoding="utf-8") as inFile:
for inLine in inFile:
theWord = inLine.strip()
if len(theWord) == 0:
continue
self.listBox.addItem(theWord)
return True
def _saveGuiSettings(self):
"""Save GUI settings.
"""
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
self.optState.setValue("GuiWordList", "winWidth", winWidth)
self.optState.setValue("GuiWordList", "winHeight", winHeight)
return
# END Class GuiWordList