From bd9463fe5856b111cf01225b6bbd84427ba4bd52 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 17:36:28 +0200
Subject: [PATCH 01/28] Added version scheme comment to main package init file
---
nw/__init__.py | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/nw/__init__.py b/nw/__init__.py
index 72327b2b..6924f94d 100644
--- a/nw/__init__.py
+++ b/nw/__init__.py
@@ -35,6 +35,28 @@ from PyQt5.QtWidgets import QApplication, QErrorMessage
from nw.error import exceptionHandler
from nw.config import Config
+#
+# Version Scheme
+# ================
+# Generally follows PEP 440
+# Hex Version:
+# - Digit 1,2 : Major Version (01, 02, 03)
+# = Digit 3,4 : Minor Version (01, 09, 10, 99)
+# - Digit 5,6 : Patch Version (01, 09, 10, 99)
+# = Digit 7 : Release Type (a: aplha, b: beta, c: candidate, f: final)
+# - Digit 8 : Release Number (0-9)
+#
+# Example : Full Short Description
+# -------------------------------------------------------------------------
+# 0x010200a0 : 1.2-alpha0 1.2a0 Can be used for the dev branch
+# 0x010200a1 : 1.2-alpha1 1.2a1 First alpha release
+# 0x010200b1 : 1.2-beta1 1.2b1 First beta release
+# 0x010200c1 : 1.2-rc1 1.2rc1 First release candidate
+# 0x010200f0 : 1.2 1.2 Final release
+# 0x010200f1 : 1.2-post1 1.2.post1 Post release, but not a code patch!
+#
+# 0x010201f0 : 1.2.1 1.2.1 Patch release
+
__package__ = "nw"
__author__ = "Veronica Berglyd Olsen"
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
From 8e9fb586771819e9a1cd3795e409966e8d5fa357 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 17:43:58 +0200
Subject: [PATCH 02/28] Swapped lines in source code
---
nw/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/nw/__init__.py b/nw/__init__.py
index 6924f94d..3f78d540 100644
--- a/nw/__init__.py
+++ b/nw/__init__.py
@@ -54,8 +54,8 @@ from nw.config import Config
# 0x010200c1 : 1.2-rc1 1.2rc1 First release candidate
# 0x010200f0 : 1.2 1.2 Final release
# 0x010200f1 : 1.2-post1 1.2.post1 Post release, but not a code patch!
-#
# 0x010201f0 : 1.2.1 1.2.1 Patch release
+#
__package__ = "nw"
__author__ = "Veronica Berglyd Olsen"
From d973f28755c2a69893127ab8d83d659cf25f12a3 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 18:39:15 +0200
Subject: [PATCH 03/28] Code and comment cleanup in main GUI
---
nw/core/document.py | 4 +--
nw/core/project.py | 4 +--
nw/guimain.py | 67 ++++++++++++++++++++++++++++-----------------
3 files changed, 46 insertions(+), 29 deletions(-)
diff --git a/nw/core/document.py b/nw/core/document.py
index 1261c12d..00fd7146 100644
--- a/nw/core/document.py
+++ b/nw/core/document.py
@@ -120,7 +120,7 @@ class NWDoc():
logger.verbose("DocMeta: '%s'" % self._docMeta)
if showStatus and not isOrphan:
- self.theParent.statusBar.setStatus("Opened Document: %s" % self._theItem.itemName)
+ self.theParent.setStatus("Opened Document: %s" % self._theItem.itemName)
return theText
@@ -166,7 +166,7 @@ class NWDoc():
os.unlink(docPath)
os.rename(docTemp, docPath)
- self.theParent.statusBar.setStatus("Saved Document: %s" % self._theItem.itemName)
+ self.theParent.setStatus("Saved Document: %s" % self._theItem.itemName)
return True
diff --git a/nw/core/project.py b/nw/core/project.py
index 46e37202..15fd7d27 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -782,7 +782,7 @@ class NWProject():
"""Create a zip file of the entire project.
"""
logger.info("Backing up project")
- self.theParent.statusBar.setStatus("Backing up project ...")
+ self.theParent.setStatus("Backing up project ...")
if self.mainConf.backupPath is None or self.mainConf.backupPath == "":
self.theParent.makeAlert((
@@ -847,7 +847,7 @@ class NWProject():
)
return False
- self.theParent.statusBar.setStatus("Project backed up to '%s.zip'" % baseName)
+ self.theParent.setStatus("Project backed up to '%s.zip'" % baseName)
return True
diff --git a/nw/guimain.py b/nw/guimain.py
index 18801727..6742a0d9 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -62,7 +62,9 @@ class GuiMain(QMainWindow):
self.mainConf = nw.CONFIG
self.threadPool = QThreadPool()
- # Some runtime info useful for debugging
+ # System Info
+ # ===========
+
logger.info("OS: %s" % self.mainConf.osType)
logger.info("Kernel: %s" % self.mainConf.kernelVer)
logger.info("Host: %s" % self.mainConf.hostName)
@@ -76,6 +78,9 @@ class GuiMain(QMainWindow):
self.mainConf.verPyString, self.mainConf.verPyHexVal)
)
+ # Core Classes
+ # ============
+
# Core Classes and settings
self.theTheme = GuiTheme(self)
self.theProject = NWProject(self)
@@ -89,7 +94,7 @@ class GuiMain(QMainWindow):
self.setWindowIcon(QIcon(self.mainConf.appIcon))
# Build the GUI
- ################
+ # =============
# Main GUI Elements
self.statusBar = GuiMainStatus(self)
@@ -106,7 +111,7 @@ class GuiMain(QMainWindow):
self.statusIcons = []
self.importIcons = []
- # Assemble Main Window
+ # Project Tree View
self.treePane = QWidget()
self.treeBox = QVBoxLayout()
self.treeBox.setContentsMargins(0, 0, 0, 0)
@@ -114,20 +119,24 @@ class GuiMain(QMainWindow):
self.treeBox.addWidget(self.treeMeta)
self.treePane.setLayout(self.treeBox)
+ # Splitter : Document Viewer / Document Meta
self.splitView = QSplitter(Qt.Vertical)
self.splitView.addWidget(self.docViewer)
self.splitView.addWidget(self.viewMeta)
self.splitView.setSizes(self.mainConf.getViewPanePos())
+ # Splitter : Document Editor / Document Viewer
self.splitDocs = QSplitter(Qt.Horizontal)
self.splitDocs.addWidget(self.docEditor)
self.splitDocs.addWidget(self.splitView)
+ # Splitter : Project Outlie / Outline Details
self.splitOutline = QSplitter(Qt.Vertical)
self.splitOutline.addWidget(self.projView)
self.splitOutline.addWidget(self.projMeta)
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
+ # Main Tabs : Edirot / Outline
self.tabWidget = QTabWidget()
self.tabWidget.setTabPosition(QTabWidget.East)
self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}")
@@ -135,6 +144,7 @@ class GuiMain(QMainWindow):
self.tabWidget.addTab(self.splitOutline, "Outline")
self.tabWidget.currentChanged.connect(self._mainTabChanged)
+ # Splitter : Project Tree / Main Tabs
xCM = self.mainConf.pxInt(4)
self.splitMain = QSplitter(Qt.Horizontal)
self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM)
@@ -142,6 +152,7 @@ class GuiMain(QMainWindow):
self.splitMain.addWidget(self.tabWidget)
self.splitMain.setSizes(self.mainConf.getMainPanePos())
+ # Indices of All Splitter Widgets
self.idxTree = self.splitMain.indexOf(self.treePane)
self.idxMain = self.splitMain.indexOf(self.tabWidget)
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
@@ -151,6 +162,7 @@ class GuiMain(QMainWindow):
self.idxTabEdit = self.tabWidget.indexOf(self.splitDocs)
self.idxTabProj = self.tabWidget.indexOf(self.splitOutline)
+ # Splitter Behaviour
self.splitMain.setCollapsible(self.idxTree, False)
self.splitMain.setCollapsible(self.idxMain, False)
self.splitDocs.setCollapsible(self.idxEditor, False)
@@ -158,10 +170,11 @@ class GuiMain(QMainWindow):
self.splitView.setCollapsible(self.idxViewDoc, False)
self.splitView.setCollapsible(self.idxViewMeta, False)
+ # Editor / Viewer Default State
self.splitView.setVisible(False)
self.docEditor.closeSearch()
- # Build the Tree View
+ # Initialise the Project Tree
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.rebuildTree()
@@ -172,13 +185,13 @@ class GuiMain(QMainWindow):
self.setStatusBar(self.statusBar)
# Finalise Initialisation
- ##########################
+ # =======================
- # Set Up Autosaving Project Timer
+ # Set Up Auto-Save Project Timer
self.asProjTimer = QTimer()
self.asProjTimer.timeout.connect(self._autoSaveProject)
- # Set Up Autosaving Document Timer
+ # Set Up Auto-Save Document Timer
self.asDocTimer = QTimer()
self.asDocTimer.timeout.connect(self._autoSaveDocument)
@@ -203,11 +216,13 @@ class GuiMain(QMainWindow):
# Check that config loaded fine
self.reportConfErr()
+ # Initialise Main GUI
self.initMain()
self.asProjTimer.start()
self.asDocTimer.start()
self.statusBar.clearStatus()
+ # Handle Windows Mode
self.showNormal()
if self.mainConf.isFullScreen:
self.toggleFullScreenMode()
@@ -224,7 +239,7 @@ class GuiMain(QMainWindow):
self.showProjectLoadDialog()
logger.debug("novelWriter is ready ...")
- self.statusBar.setStatus("novelWriter is ready ...")
+ self.setStatus("novelWriter is ready ...")
return
@@ -249,8 +264,7 @@ class GuiMain(QMainWindow):
##
def newProject(self, projData=None):
- """Create new project with a few default files and folders.
- The variable forceNew is used for testing.
+ """Create new project via the new project wizard.
"""
if self.hasProject:
self.makeAlert(
@@ -293,7 +307,7 @@ class GuiMain(QMainWindow):
def closeProject(self, isYes=False):
"""Closes the project if one is open. isYes is passed on from
the close application event so the user doesn't get prompted
- twice.
+ twice to confirm.
"""
if not self.hasProject:
# There is no project loaded, everything OK
@@ -302,7 +316,7 @@ class GuiMain(QMainWindow):
if not isYes:
msgBox = QMessageBox()
msgRes = msgBox.question(
- self, "Close Project", "Save changes and close current project?"
+ self, "Close Project", "Save changes and close the current project?"
)
if msgRes != QMessageBox.Yes:
return False
@@ -318,7 +332,7 @@ class GuiMain(QMainWindow):
if self.mainConf.askBeforeBackup:
msgBox = QMessageBox()
msgRes = msgBox.question(
- self, "Backup Project", "Backup current project?"
+ self, "Backup Project", "Backup the current project?"
)
if msgRes != QMessageBox.Yes:
doBackup = False
@@ -433,6 +447,7 @@ class GuiMain(QMainWindow):
if self.theProject.projPath is None:
projPath = self.selectProjectPath()
self.theProject.setProjectPath(projPath)
+
if self.theProject.projPath is None:
return False
@@ -454,6 +469,7 @@ class GuiMain(QMainWindow):
if self.docEditor.docChanged:
self.saveDocument()
self.docEditor.clearEditor()
+
return True
def openDocument(self, tHandle, tLine=None, changeFocus=True, doScroll=False):
@@ -469,6 +485,7 @@ class GuiMain(QMainWindow):
self.treeView.setSelectedHandle(tHandle, doScroll=doScroll)
else:
return False
+
return True
def openNextDocument(self, tHandle, wrapAround=False):
@@ -546,6 +563,7 @@ class GuiMain(QMainWindow):
vPos[1] = bPos[1] - vPos[0]
self.splitDocs.setSizes(vPos)
self.viewMeta.setVisible(self.mainConf.showRefPanel)
+
self.docViewer.navigateTo(tAnchor)
return True
@@ -697,9 +715,9 @@ class GuiMain(QMainWindow):
for nDone, tItem in enumerate(self.theProject.projTree):
if tItem is not None:
- self.statusBar.setStatus("Indexing: '%s'" % tItem.itemName)
+ self.setStatus("Indexing: '%s'" % tItem.itemName)
else:
- self.statusBar.setStatus("Indexing: Unknown item")
+ self.setStatus("Indexing: Unknown item")
if tItem is not None and tItem.itemType == nwItemType.FILE:
logger.verbose("Scanning: %s" % tItem.itemName)
@@ -717,7 +735,7 @@ class GuiMain(QMainWindow):
self.treeView.projectWordCount()
tEnd = time()
- self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
+ self.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
self.docEditor.updateTagHighLighting()
qApp.restoreOverrideCursor()
@@ -754,7 +772,8 @@ class GuiMain(QMainWindow):
def showProjectLoadDialog(self):
"""Opens the projects dialog for selecting either existing
projects from a cache of recently opened projects, or provide a
- browse button for projects not yet cached.
+ browse button for projects not yet cached. Selecting to create a
+ new project is forwarded to the new project wizard.
"""
dlgProj = GuiProjectLoad(self)
dlgProj.exec_()
@@ -767,7 +786,7 @@ class GuiMain(QMainWindow):
return True
def showNewProjectDialog(self):
- """Open the wizard and assemble the project options dict.
+ """Open the wizard and assemble a project options dict.
"""
newProj = GuiProjectWizard(self)
newProj.exec_()
@@ -865,8 +884,7 @@ class GuiMain(QMainWindow):
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
"""Alert both the user and the logger at the same time. Message
- can be either a string or an array of strings. Severity level is
- 0 = info, 1 = warning, and 2 = error.
+ can be either a string or an array of strings.
"""
if isinstance(theMessage, list):
popMsg = "
".join(theMessage)
@@ -955,7 +973,7 @@ class GuiMain(QMainWindow):
return True
def setFocus(self, paneNo):
- """Switch focus to one of the three main gUi panes.
+ """Switch focus to one of the three main GUI panes.
"""
if paneNo == 1:
self.treeView.setFocus()
@@ -1236,9 +1254,9 @@ class GuiMain(QMainWindow):
return
def _treeKeyPressReturn(self):
- """The user pressed return an item in the tree. If it is a file,
- we open it. Otherwise, we do nothing. Pressing return does not
- change focus to the editor as double click does.
+ """The user pressed return on an item in the tree. If it is a
+ file, we open it. Otherwise, we do nothing. Pressing return does
+ not change focus to the editor as double click does.
"""
tHandle = self.treeView.getSelectedHandle()
logger.verbose("User pressed return on tree item with handle %s" % tHandle)
@@ -1257,7 +1275,6 @@ class GuiMain(QMainWindow):
"""
if self.docEditor.docSearch.isVisible():
self.docEditor.closeSearch()
- return
elif self.isFocusMode:
self.toggleFocusMode()
return
From 1923a55443cc5e861718912c036777e8fafd4099 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 19:08:35 +0200
Subject: [PATCH 04/28] Code cleanup in core classes and rename of parHandle to
itemParent in NWItem class
---
nw/core/document.py | 11 +++++++----
nw/core/index.py | 6 +++---
nw/core/item.py | 19 +++++++++++--------
nw/core/project.py | 6 +++---
nw/core/tree.py | 10 +++++-----
nw/gui/build.py | 4 ++--
nw/gui/docmerge.py | 2 +-
nw/gui/docsplit.py | 4 ++--
nw/gui/projtree.py | 12 ++++++------
tests/test_item.py | 10 +++++-----
tests/test_project.py | 4 ++--
11 files changed, 47 insertions(+), 41 deletions(-)
diff --git a/nw/core/document.py b/nw/core/document.py
index 00fd7146..1452e9dd 100644
--- a/nw/core/document.py
+++ b/nw/core/document.py
@@ -67,7 +67,9 @@ class NWDoc():
def openDocument(self, tHandle, showStatus=True, isOrphan=False):
"""Open a document from handle, capturing potential file system
- errors and parse meta data.
+ errors and parse meta data. If the document doesn't exist on
+ disk, return an empty string. If something went wrong, return
+ None.
"""
if not isHandle(tHandle):
return None
@@ -125,8 +127,8 @@ class NWDoc():
return theText
def saveDocument(self, docText):
- """Save the document via temp file in case of save failure, and
- in any case keep a backup of the file.
+ """Save the document. The file is saved via a temp file in case
+ of save failure. Returns True if successful, False if not.
"""
if self._docHandle is None:
return False
@@ -139,6 +141,7 @@ class NWDoc():
docPath = os.path.join(self.theProject.projContent, docFile)
docTemp = os.path.join(self.theProject.projContent, docFile+"~")
+ # DocMeta line
if self._theItem is None:
docMeta = ""
else:
@@ -171,7 +174,7 @@ class NWDoc():
return True
def deleteDocument(self, tHandle):
- """Permanently delete a document source file and its backups
+ """Permanently delete a document source file and related files
from the project data folder.
"""
if not isHandle(tHandle):
diff --git a/nw/core/index.py b/nw/core/index.py
index f715d835..c08562e1 100644
--- a/nw/core/index.py
+++ b/nw/core/index.py
@@ -279,7 +279,7 @@ class NWIndex():
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
logger.info("Not indexing no-layout item %s" % tHandle)
return False
- if theItem.parHandle is None:
+ if theItem.itemParent is None:
logger.info("Not indexing orphaned item %s" % tHandle)
return False
@@ -288,7 +288,7 @@ class NWIndex():
self.textCounts[tHandle] = [cC, wC, pC]
# If the file is archived or trashed, we don't index the file itself
- if self.theProject.projTree.isTrashRoot(theItem.parHandle):
+ if self.theProject.projTree.isTrashRoot(theItem.itemParent):
logger.info("Not indexing trash item %s" % tHandle)
return False
if theRoot.itemClass == nwItemClass.ARCHIVE:
@@ -583,7 +583,7 @@ class NWIndex():
def getCounts(self, tHandle, sTitle=None):
"""Returns the counts for a file, or a section of a file
- starting at title nTitle.
+ starting at title sTitle if it is provided.
"""
cC = 0
wC = 0
diff --git a/nw/core/item.py b/nw/core/item.py
index 702287dd..944b5f0c 100644
--- a/nw/core/item.py
+++ b/nw/core/item.py
@@ -42,7 +42,7 @@ class NWItem():
self.itemName = ""
self.itemHandle = None
- self.parHandle = None
+ self.itemParent = None
self.itemOrder = None
self.itemType = nwItemType.NO_TYPE
self.itemClass = nwItemClass.NO_CLASS
@@ -70,7 +70,7 @@ class NWItem():
xPack = etree.SubElement(xParent, "item", attrib={
"handle" : str(self.itemHandle),
"order" : str(self.itemOrder),
- "parent" : str(self.parHandle),
+ "parent" : str(self.itemParent),
})
self._subPack(xPack, "name", text=str(self.itemName))
self._subPack(xPack, "type", text=str(self.itemType.name))
@@ -85,6 +85,7 @@ class NWItem():
self._subPack(xPack, "cursorPos", text=str(self.cursorPos), none=False)
else:
self._subPack(xPack, "expanded", text=str(self.isExpanded))
+
return
def unpackXML(self, xItem):
@@ -101,7 +102,7 @@ class NWItem():
return False
if "parent" in xItem.attrib:
- self.parHandle = xItem.attrib["parent"]
+ self.itemParent = xItem.attrib["parent"]
setMap = {
"name" : self.setName,
@@ -131,9 +132,11 @@ class NWItem():
"""
if not none and (text is None or text == "None"):
return None
- xSub = etree.SubElement(xParent, name, attrib=attrib)
+ xAttr = {} if attrib is None else attrib
+ xSub = etree.SubElement(xParent, name, attrib=xAttr)
if text is not None:
xSub.text = text
+
return
##
@@ -162,14 +165,14 @@ class NWItem():
"""Set the parent handle, and ensure that it is valid.
"""
if theParent is None:
- self.parHandle = None
+ self.itemParent = None
elif isinstance(theParent, str):
if len(theParent) == 13:
- self.parHandle = theParent
+ self.itemParent = theParent
else:
- self.parHandle = None
+ self.itemParent = None
else:
- self.parHandle = None
+ self.itemParent = None
return
def setOrder(self, theOrder):
diff --git a/nw/core/project.py b/nw/core/project.py
index 15fd7d27..622bcc38 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -1139,16 +1139,16 @@ class NWProject():
# Technically a bug since treeOrder is built from the
# same data as projTree
continue
- elif tItem.parHandle is None:
+ elif tItem.itemParent is None:
# Item is a root, or already been identified as an
# orphaned item
sentItems.append(tHandle)
yield tItem
- elif tItem.parHandle in sentItems:
+ elif tItem.itemParent in sentItems:
# Item's parent has been sent, so all is fine
sentItems.append(tHandle)
yield tItem
- elif tItem.parHandle in iterItems:
+ elif tItem.itemParent in iterItems:
# Item's parent exists, but hasn't been sent yet, so add
# it again to the end
logger.warning("Item %s found before its parent" % tHandle)
diff --git a/nw/core/tree.py b/nw/core/tree.py
index c51ae16c..b43230a9 100644
--- a/nw/core/tree.py
+++ b/nw/core/tree.py
@@ -134,7 +134,7 @@ class NWTree():
for xItem in xContent:
nwItem = NWItem(self.theProject)
if nwItem.unpackXML(xItem):
- self.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
+ self.append(nwItem.itemHandle, nwItem.itemParent, nwItem)
nwItem.saveInitialCount()
return True
@@ -261,10 +261,10 @@ class NWTree():
tItem = self.__getitem__(tHandle)
if tItem is not None:
for i in range(nwConst.maxDepth + 1):
- if tItem.parHandle is None:
+ if tItem.itemParent is None:
return tItem
else:
- tHandle = tItem.parHandle
+ tHandle = tItem.itemParent
tItem = self.__getitem__(tHandle)
return None
@@ -279,10 +279,10 @@ class NWTree():
if tItem is not None:
tTree.append(tHandle)
for i in range(nwConst.maxDepth + 1):
- if tItem.parHandle is None:
+ if tItem.itemParent is None:
return tTree
else:
- tHandle = tItem.parHandle
+ tHandle = tItem.itemParent
tItem = self.__getitem__(tHandle)
if tItem is None:
return tTree
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 1a69b62e..45a1a073 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -684,8 +684,8 @@ class GuiBuildNovel(QDialog):
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
isNone |= theItem.itemClass == nwItemClass.TRASH
- isNone |= theItem.parHandle == self.theProject.projTree.trashRoot()
- isNone |= theItem.parHandle is None
+ isNone |= theItem.itemParent == self.theProject.projTree.trashRoot()
+ isNone |= theItem.itemParent is None
isNote = theItem.itemLayout == nwItemLayout.NOTE
isNovel = not isNone and not isNote
diff --git a/nw/gui/docmerge.py b/nw/gui/docmerge.py
index 2418a161..d7d128fe 100644
--- a/nw/gui/docmerge.py
+++ b/nw/gui/docmerge.py
@@ -127,7 +127,7 @@ class GuiDocMerge(QDialog):
), nwAlert.ERROR)
return
- nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.parHandle)
+ nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
newItem = self.theProject.projTree[nHandle]
newItem.setStatus(srcItem.itemStatus)
diff --git a/nw/gui/docsplit.py b/nw/gui/docsplit.py
index 3f497c18..1a5e9491 100644
--- a/nw/gui/docsplit.py
+++ b/nw/gui/docsplit.py
@@ -154,7 +154,7 @@ class GuiDocSplit(QDialog):
return
# Check that another folder can be created
- parTree = self.theProject.projTree.getItemPath(srcItem.parHandle)
+ parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
if len(parTree) >= nwConst.maxDepth - 1:
self.theParent.makeAlert((
"Cannot add new folder for the document split. "
@@ -176,7 +176,7 @@ class GuiDocSplit(QDialog):
# Create the folder
fHandle = self.theProject.newFolder(
- srcItem.itemName, srcItem.itemClass, srcItem.parHandle
+ srcItem.itemName, srcItem.itemClass, srcItem.itemParent
)
self.theParent.treeView.revealNewTreeItem(fHandle)
logger.verbose("Creating folder %s" % fHandle)
diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py
index 9f4bb5e6..6b1ee7bd 100644
--- a/nw/gui/projtree.py
+++ b/nw/gui/projtree.py
@@ -219,7 +219,7 @@ class GuiProjectTree(QTreeWidget):
pItem = self.theProject.projTree[pHandle]
if pItem.itemType == nwItemType.FILE:
nHandle = pHandle
- pHandle = pItem.parHandle
+ pHandle = pItem.itemParent
# If we again have no home, give up
if pHandle is None:
@@ -270,7 +270,7 @@ class GuiProjectTree(QTreeWidget):
"""
nwItem = self.theProject.projTree[tHandle]
trItem = self._addTreeItem(nwItem, nHandle)
- pHandle = nwItem.parHandle
+ pHandle = nwItem.itemParent
if pHandle is not None and pHandle in self.theMap:
self.theMap[pHandle].setExpanded(True)
self.clearSelection()
@@ -430,7 +430,7 @@ class GuiProjectTree(QTreeWidget):
logger.error("Could not delete item")
return False
- pHandle = nwItemS.parHandle
+ pHandle = nwItemS.itemParent
if self.theProject.projTree.isTrashRoot(pHandle):
# If the file is in the trash folder already, as the
# user if they want to permanently delete the file.
@@ -815,7 +815,7 @@ class GuiProjectTree(QTreeWidget):
project tree.
"""
tHandle = nwItem.itemHandle
- pHandle = nwItem.parHandle
+ pHandle = nwItem.itemParent
tClass = nwItem.itemClass
newItem = QTreeWidgetItem([""]*4)
@@ -1022,11 +1022,11 @@ class GuiProjectTreeMenu(QMenu):
trashHandle = self.theTree.theProject.projTree.trashRoot()
- inTrash = theItem.parHandle == trashHandle and trashHandle is not None
+ inTrash = theItem.itemParent == trashHandle and trashHandle is not None
isTrash = theItem.itemHandle == trashHandle and trashHandle is not None
isFile = theItem.itemType == nwItemType.FILE
isArch = theRoot.itemClass == nwItemClass.ARCHIVE
- isOrph = isFile and theItem.parHandle is None
+ isOrph = isFile and theItem.itemParent is None
showOpen = isFile
showView = isFile
diff --git a/tests/test_item.py b/tests/test_item.py
index d1097161..936ba185 100644
--- a/tests/test_item.py
+++ b/tests/test_item.py
@@ -31,13 +31,13 @@ def testItemSettersSimple(nwDummy):
# Parent
theItem.setParent(None)
- assert theItem.parHandle is None
+ assert theItem.itemParent is None
theItem.setParent(123)
- assert theItem.parHandle is None
+ assert theItem.itemParent is None
theItem.setParent("0123456789abcdef")
- assert theItem.parHandle is None
+ assert theItem.itemParent is None
theItem.setParent("0123456789abc")
- assert theItem.parHandle == "0123456789abc"
+ assert theItem.itemParent == "0123456789abc"
# Order
theItem.setOrder(None)
@@ -227,7 +227,7 @@ def testItemXMLPackUnpack(nwDummy):
# Unpack
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
- assert theItem.parHandle == "0123456789abc"
+ assert theItem.itemParent == "0123456789abc"
assert theItem.itemOrder == 1
assert theItem.isExpanded
assert theItem.paraCount == 3
diff --git a/tests/test_project.py b/tests/test_project.py
index 282acbc7..e8f1442b 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -521,7 +521,7 @@ def testProjectOrphanedFiles(nwDummy, nwLipsum):
assert oItem is not None
assert oItem.itemName == "Mars"
assert oItem.itemHandle == "636b6aa9b697b"
- assert oItem.parHandle is None
+ assert oItem.itemParent is None
assert oItem.itemClass == nwItemClass.WORLD
assert oItem.itemType == nwItemType.FILE
assert oItem.itemLayout == nwItemLayout.NOTE
@@ -531,7 +531,7 @@ def testProjectOrphanedFiles(nwDummy, nwLipsum):
assert oItem is not None
assert oItem.itemName == "Orphaned File 1"
assert oItem.itemHandle == "736b6aa9b697b"
- assert oItem.parHandle is None
+ assert oItem.itemParent is None
assert oItem.itemClass == nwItemClass.NO_CLASS
assert oItem.itemType == nwItemType.FILE
assert oItem.itemLayout == nwItemLayout.NO_LAYOUT
From bf7429f028419951ae33323f7aeb86dfe313ff7f Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 20:33:10 +0200
Subject: [PATCH 05/28] Update format statements and improve status bar clock
---
.github/workflows/syntax.yml | 4 ++--
nw/gui/about.py | 1 -
nw/gui/doceditor.py | 19 +++++++++----------
nw/gui/dochighlight.py | 10 +++++++---
nw/gui/outline.py | 10 +++++++---
nw/gui/outlinedetails.py | 10 +++++++---
nw/gui/projsettings.py | 7 ++++---
nw/gui/statusbar.py | 21 +++++++++------------
setup.cfg | 2 +-
9 files changed, 46 insertions(+), 38 deletions(-)
diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml
index 038480f7..bf8ffec3 100644
--- a/.github/workflows/syntax.yml
+++ b/.github/workflows/syntax.yml
@@ -25,5 +25,5 @@ jobs:
flake8 tests --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Coding Style Violations
run: |
- flake8 nw --count --max-line-length=99 --ignore E203,E221,E226,E241,E251,E261,E266,E302,E305 --show-source --statistics
- flake8 tests --count --max-line-length=99 --ignore E203,E221,E226,E241,E251,E261,E266,E302,E305 --show-source --statistics
+ flake8 nw --count --max-line-length=99 --ignore E203,E221,E226,E228,E241,E251,E261,E266,E302,E305 --show-source --statistics
+ flake8 tests --count --max-line-length=99 --ignore E203,E221,E226,E228,E241,E251,E261,E266,E302,E305 --show-source --statistics
diff --git a/nw/gui/about.py b/nw/gui/about.py
index c6957bae..248cf7d1 100644
--- a/nw/gui/about.py
+++ b/nw/gui/about.py
@@ -222,7 +222,6 @@ class GuiAbout(QDialog):
hColB = self.theParent.theTheme.colHead[2],
)
self.pageAbout.document().setDefaultStyleSheet(styleSheet)
- # self.pageCredit.document().setDefaultStyleSheet(styleSheet)
self.pageLicense.document().setDefaultStyleSheet(styleSheet)
return
diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py
index d5e3b045..6d431319 100644
--- a/nw/gui/doceditor.py
+++ b/nw/gui/doceditor.py
@@ -807,16 +807,16 @@ class GuiDocEditor(QTextEdit):
return self.docSearch.cycleFocus(toNext)
return True
- def mouseReleaseEvent(self, mEvent):
+ def mouseReleaseEvent(self, theEvent):
"""If the mouse button is released and the control key is
pressed, check if we're clicking on a tag, and trigger the
follow tag function.
"""
if qApp.keyboardModifiers() == Qt.ControlModifier:
- theCursor = self.cursorForPosition(mEvent.pos())
+ theCursor = self.cursorForPosition(theEvent.pos())
self._followTag(theCursor)
- QTextEdit.mouseReleaseEvent(self, mEvent)
+ QTextEdit.mouseReleaseEvent(self, theEvent)
self.docFooter.updateLineCount()
return
@@ -1203,20 +1203,19 @@ class GuiDocEditor(QTextEdit):
"""Check if document size crosses the big document limit set in
config. If so, we will set the big document flag to True.
"""
- newState = theSize > self.mainConf.bigDocLimit*1000
+ bigLim = self.mainConf.bigDocLimit*1000
+ newState = theSize > bigLim
if newState != self.bigDoc:
if newState:
logger.info(
- "The document size is {:n} > {:n}, big doc mode has been enabled".format(
- theSize, self.mainConf.bigDocLimit*1000
- )
+ f"The document size is {theSize:n} > {bigLim:n}, "
+ f"big doc mode has been enabled"
)
else:
logger.info(
- "The document size is {:n} <= {:n}, big doc mode has been disabled".format(
- theSize, self.mainConf.bigDocLimit*1000
- )
+ f"The document size is {theSize:n} <= {bigLim:n}, "
+ f"big doc mode has been disabled"
)
self.bigDoc = newState
diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py
index ba39b6e6..cc11ef86 100644
--- a/nw/gui/dochighlight.py
+++ b/nw/gui/dochighlight.py
@@ -149,18 +149,22 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Quoted Strings
if self.mainConf.highlightQuotes:
+ fmtDO = self.mainConf.fmtDoubleQuotes[0]
+ fmtDC = self.mainConf.fmtDoubleQuotes[1]
+ fmtSO = self.mainConf.fmtSingleQuotes[0]
+ fmtSC = self.mainConf.fmtSingleQuotes[1]
self.hRules.append((
- "\\B{:s}(.*?){:s}\\B".format('"', '"'), {
+ "\\B\"(.*?)\"\\B", {
0 : self.hStyles["dialogue1"],
}
))
self.hRules.append((
- "\\B{:s}(.*?){:s}\\B".format(*self.mainConf.fmtDoubleQuotes), {
+ f"\\B{fmtDO:s}(.*?){fmtDC:s}\\B", {
0 : self.hStyles["dialogue2"],
}
))
self.hRules.append((
- "\\B{:s}(.*?){:s}\\B".format(*self.mainConf.fmtSingleQuotes), {
+ f"\\B{fmtSO:s}(.*?){fmtSC:s}\\B", {
0 : self.hStyles["dialogue3"],
}
))
diff --git a/nw/gui/outline.py b/nw/gui/outline.py
index d3dc4815..4238cc44 100644
--- a/nw/gui/outline.py
+++ b/nw/gui/outline.py
@@ -439,6 +439,10 @@ class GuiOutline(QTreeWidget):
newItem = QTreeWidgetItem()
hIcon = "doc_%s" % tLevel.lower()
+ cC = int(novIdx["cCount"])
+ wC = int(novIdx["wCount"])
+ pC = int(novIdx["pCount"])
+
newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"])
newItem.setData(self.colIndex[nwOutline.TITLE], Qt.UserRole, tHandle)
newItem.setIcon(self.colIndex[nwOutline.TITLE], self.theTheme.getIcon(hIcon))
@@ -448,9 +452,9 @@ class GuiOutline(QTreeWidget):
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0"))
newItem.setData(self.colIndex[nwOutline.LINE], Qt.UserRole, sTitle)
newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"])
- newItem.setText(self.colIndex[nwOutline.CCOUNT], "{:n}".format(novIdx["cCount"]))
- newItem.setText(self.colIndex[nwOutline.WCOUNT], "{:n}".format(novIdx["wCount"]))
- newItem.setText(self.colIndex[nwOutline.PCOUNT], "{:n}".format(novIdx["pCount"]))
+ newItem.setText(self.colIndex[nwOutline.CCOUNT], f"{cC:n}")
+ newItem.setText(self.colIndex[nwOutline.WCOUNT], f"{wC:n}")
+ newItem.setText(self.colIndex[nwOutline.PCOUNT], f"{pC:n}")
newItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight)
newItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight)
newItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight)
diff --git a/nw/gui/outlinedetails.py b/nw/gui/outlinedetails.py
index 49998dd9..4f6a56f5 100644
--- a/nw/gui/outlinedetails.py
+++ b/nw/gui/outlinedetails.py
@@ -266,9 +266,13 @@ class GuiOutlineDetails(QScrollArea):
self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(nwItem.itemStatus)
- self.cCValue.setText("{:n}".format(checkInt(novIdx["cCount"], 0)))
- self.wCValue.setText("{:n}".format(checkInt(novIdx["wCount"], 0)))
- self.pCValue.setText("{:n}".format(checkInt(novIdx["pCount"], 0)))
+ cC = checkInt(novIdx["cCount"], 0)
+ wC = checkInt(novIdx["wCount"], 0)
+ pC = checkInt(novIdx["pCount"], 0)
+
+ self.cCValue.setText(f"{cC:n}")
+ self.wCValue.setText(f"{wC:n}")
+ self.pCValue.setText(f"{pC:n}")
self.synopValue.setText(novIdx["synopsis"])
diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py
index 30d9ad54..d51d8006 100644
--- a/nw/gui/projsettings.py
+++ b/nw/gui/projsettings.py
@@ -270,11 +270,12 @@ class GuiProjectEditMeta(QWidget):
self.revLabel = QLabel("Revision count:")
self.revLabel.setIndent(xInd)
- self.revValue = QLabel("{:n}".format(self.theProject.saveCount))
+ self.revValue = QLabel(f"{self.theProject.saveCount:n}")
+ editHours = self.theProject.editTime/3600
self.editLabel = QLabel("Edit time:")
self.editLabel.setIndent(xInd)
- self.editValue = QLabel("{:.2f} hours".format(self.theProject.editTime/3600))
+ self.editValue = QLabel(f"{editHours:.2f} hours")
self.statsLabel = QLabel("Project Stats")
@@ -294,7 +295,7 @@ class GuiProjectEditMeta(QWidget):
self.wordsLabel = QLabel("Word count:")
self.wordsLabel.setIndent(xInd)
- self.wordsValue = QLabel("{:n}".format(self.theProject.currWCount))
+ self.wordsValue = QLabel(f"{self.theProject.currWCount:n}")
self.mainForm.addWidget(self.headLabel, 0, 0, 1, 2, Qt.AlignTop)
self.mainForm.addWidget(self.nameLabel, 1, 0, 1, 1, Qt.AlignTop)
diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py
index c43c3f6f..91a86519 100644
--- a/nw/gui/statusbar.py
+++ b/nw/gui/statusbar.py
@@ -196,10 +196,7 @@ class GuiMainStatus(QStatusBar):
"Project word count (session change)"
)
self.statsText.setText((
- "Words: {pWC:n} ({sWC:+n})"
- ).format(
- pWC = self.projWords,
- sWC = self.sessWords,
+ f"Words: {self.projWords:n} ({self.sessWords:+n})"
))
return
@@ -207,16 +204,16 @@ class GuiMainStatus(QStatusBar):
"""Update the session clock.
"""
if self.refTime is None:
- theTime = "00:00:00"
+ self.timeText.setText("00:00:00")
else:
# This is much faster than using datetime format
tS = int(time() - self.refTime)
- tM = int(tS/60)
- tH = int(tM/60)
- tM = tM - tH*60
- tS = tS - tM*60 - tH*3600
- theTime = "%02d:%02d:%02d" % (tH, tM, tS)
- self.timeText.setText(theTime)
+ tM = tS//60
+ tH = tM//60
+ tM %= 60
+ tS %= 60
+ self.timeText.setText(f"{tH:02d}:{tM:02d}:{tS:02d}")
+
return
# END Class GuiMainStatus
@@ -237,7 +234,7 @@ class StatusLED(QAbstractButton):
return
##
- # Getters and Setters
+ # Setters
##
def setState(self, theState):
diff --git a/setup.cfg b/setup.cfg
index a791e31e..ff0117c5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,6 +6,6 @@ version = attr: nw.__version__
universal = 0
[flake8]
-ignore = E203,E221,E226,E241,E251,E261,E266,E302,E305
+ignore = E203,E221,E226,E228,E241,E251,E261,E266,E302,E305
max-line-length = 99
exclude = docs/*
From e0d15a882ea241832f143c6e4a9876690b5980fe Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 20:56:09 +0200
Subject: [PATCH 06/28] Further improvements to the status bar clock
---
nw/gui/statusbar.py | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py
index 91a86519..da8a0427 100644
--- a/nw/gui/statusbar.py
+++ b/nw/gui/statusbar.py
@@ -206,14 +206,8 @@ class GuiMainStatus(QStatusBar):
if self.refTime is None:
self.timeText.setText("00:00:00")
else:
- # This is much faster than using datetime format
tS = int(time() - self.refTime)
- tM = tS//60
- tH = tM//60
- tM %= 60
- tS %= 60
- self.timeText.setText(f"{tH:02d}:{tM:02d}:{tS:02d}")
-
+ self.timeText.setText(f"{tS//3600:02d}:{(tS//60)%60:02d}:{tS%60:02d}")
return
# END Class GuiMainStatus
From 7deee064d08a04d618abecc28ace49c629d19fa6 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 22:44:47 +0200
Subject: [PATCH 07/28] More improvements, mostly string formatting
---
nw/core/tohtml.py | 1 +
nw/core/tools.py | 17 ++++++++---------
nw/gui/statusbar.py | 4 +++-
tests/test_tools.py | 1 +
4 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index 770505db..df6c4a09 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -41,6 +41,7 @@ class ToHtml(Tokenizer):
def __init__(self, theProject, theParent):
Tokenizer.__init__(self, theProject, theParent)
+
self.genMode = self.M_EXPORT
self.cssStyles = True
diff --git a/nw/core/tools.py b/nw/core/tools.py
index 59c141d9..8062d591 100644
--- a/nw/core/tools.py
+++ b/nw/core/tools.py
@@ -136,7 +136,6 @@ def numberToWord(numVal, theLanguage):
def _numberToWordEN(numVal):
"""Convert numbers to English words.
"""
- numWord = ""
oneWord = ""
tenWord = ""
hunWord = ""
@@ -145,8 +144,8 @@ def _numberToWordEN(numVal):
return "Zero"
oneVal = numVal % 10
- tenVal = (numVal-oneVal) % 100
- hunVal = (numVal-tenVal-oneVal) % 1000
+ tenVal = (numVal - oneVal) % 100
+ hunVal = (numVal - tenVal - oneVal) % 1000
theHundreds = {
100: "One Hundred", 200: "Two Hundred", 300: "Three Hundred",
@@ -167,18 +166,18 @@ def _numberToWordEN(numVal):
}
hunWord = theHundreds.get(hunVal, "")
- tenWord = theTens.get(tenVal, "")
if tenVal == 10:
oneWord = theTeens.get(oneVal, "")
- numWord = ("%s %s" % (hunWord, oneWord)).strip()
+ return f"{hunWord} {oneWord}".strip()
else:
oneWord = theOnes.get(oneVal, "")
if tenVal == 0:
- numWord = ("%s %s" % (hunWord, oneWord)).strip()
+ return f"{hunWord} {oneWord}".strip()
else:
+ tenWord = theTens.get(tenVal, "")
if oneVal == 0:
- numWord = ("%s %s" % (hunWord, tenWord)).strip()
+ return f"{hunWord} {tenWord}".strip()
else:
- numWord = ("%s %s-%s" % (hunWord, tenWord, oneWord)).strip()
+ return f"{hunWord} {tenWord}-{oneWord}".strip()
- return numWord
+ return ""
diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py
index da8a0427..b0ab87ae 100644
--- a/nw/gui/statusbar.py
+++ b/nw/gui/statusbar.py
@@ -207,7 +207,9 @@ class GuiMainStatus(QStatusBar):
self.timeText.setText("00:00:00")
else:
tS = int(time() - self.refTime)
- self.timeText.setText(f"{tS//3600:02d}:{(tS//60)%60:02d}:{tS%60:02d}")
+ self.timeText.setText(
+ f"{tS//3600:02d}:{tS%3600//60:02d}:{tS%60:02d}"
+ )
return
# END Class GuiMainStatus
diff --git a/tests/test_tools.py b/tests/test_tools.py
index bfff8f3f..4832aadb 100644
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -61,6 +61,7 @@ def testNumberWords():
assert numberToWord(21, "en") == "Twenty-One"
assert numberToWord(29, "en") == "Twenty-Nine"
assert numberToWord(42, "en") == "Forty-Two"
+ assert numberToWord(114, "en") == "One Hundred Fourteen"
assert numberToWord(142, "en") == "One Hundred Forty-Two"
assert numberToWord(999, "en") == "Nine Hundred Ninety-Nine"
From 487a1167959c9fc9414967173abd7f4dacec57f0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 15 Oct 2020 22:51:30 +0200
Subject: [PATCH 08/28] Removed some redundant parantheses
---
nw/gui/statusbar.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py
index b0ab87ae..0b95ef16 100644
--- a/nw/gui/statusbar.py
+++ b/nw/gui/statusbar.py
@@ -195,9 +195,9 @@ class GuiMainStatus(QStatusBar):
self.statsText.setToolTip(
"Project word count (session change)"
)
- self.statsText.setText((
+ self.statsText.setText(
f"Words: {self.projWords:n} ({self.sessWords:+n})"
- ))
+ )
return
def _updateTime(self):
From 062ec44890f6d9cda595bd6f0f2b003a89ef3c93 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 16 Oct 2020 14:11:02 +0200
Subject: [PATCH 09/28] Some minor fixes to the debian and ubuntu install
scripts
---
assets/installDebian.sh | 49 -------------------
...nstallUbuntu.sh => installDebianUbuntu.sh} | 0
assets/mime/x-novelwriter-project.xml | 2 +-
3 files changed, 1 insertion(+), 50 deletions(-)
delete mode 100755 assets/installDebian.sh
rename assets/{installUbuntu.sh => installDebianUbuntu.sh} (100%)
diff --git a/assets/installDebian.sh b/assets/installDebian.sh
deleted file mode 100755
index 3f5ae392..00000000
--- a/assets/installDebian.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash
-
-cd ..
-
-EXEC=$(pwd)/novelWriter.py
-EXEC=$(echo $EXEC | sed 's_/_\\/_g')
-
-sed "s/%%exec%%/$EXEC/g" assets/novelwriter.desktop > /usr/share/applications/novelwriter.desktop
-
-if [ ! -d /usr/share/icons/hicolor/24x24/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/24x24/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/48x48/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/48x48/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/96x96/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/96x96/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/256x256/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/256x256/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/512x512/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/512x512/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/scalable/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/scalable/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/scalable/mimetypes ]; then
- mkdir -pv /usr/share/icons/hicolor/scalable/mimetypes
-fi
-
-cp -v assets/icons/24x24/novelwriter.png /usr/share/icons/hicolor/24x24/apps/
-cp -v assets/icons/48x48/novelwriter.png /usr/share/icons/hicolor/48x48/apps/
-cp -v assets/icons/96x96/novelwriter.png /usr/share/icons/hicolor/96x96/apps/
-cp -v assets/icons/256x256/novelwriter.png /usr/share/icons/hicolor/256x256/apps/
-cp -v assets/icons/512x512/novelwriter.png /usr/share/icons/hicolor/512x512/apps/
-cp -v assets/icons/novelwriter.svg /usr/share/icons/hicolor/scalable/apps/
-cp -v assets/icons/x-novelwriter-project.svg /usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg
-cp -v assets/mime/x-novelwriter-project.xml /usr/share/mime/packages/
-
-update-mime-database /usr/share/mime/
-update-icon-caches /usr/share/icons/hicolor/24x24/apps/
-update-icon-caches /usr/share/icons/hicolor/48x48/apps/
-update-icon-caches /usr/share/icons/hicolor/96x96/apps/
-update-icon-caches /usr/share/icons/hicolor/256x256/apps/
-update-icon-caches /usr/share/icons/hicolor/512x512/apps/
-update-icon-caches /usr/share/icons/hicolor/1024x1024/apps/
-update-icon-caches /usr/share/icons/hicolor/scalable/apps/
-update-icon-caches /usr/share/icons/hicolor/scalable/mimetypes/
diff --git a/assets/installUbuntu.sh b/assets/installDebianUbuntu.sh
similarity index 100%
rename from assets/installUbuntu.sh
rename to assets/installDebianUbuntu.sh
diff --git a/assets/mime/x-novelwriter-project.xml b/assets/mime/x-novelwriter-project.xml
index 21ecbe0a..789c08fd 100644
--- a/assets/mime/x-novelwriter-project.xml
+++ b/assets/mime/x-novelwriter-project.xml
@@ -1,7 +1,7 @@
- novelWriter Project
+ novelWriter Project
From 0888bd70e1b73c224897bbb87a05f7e02297d4b9 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 16 Oct 2020 14:22:10 +0200
Subject: [PATCH 10/28] Renamed the root assets folder to setup to avoid mixing
it with nw/assets
---
MANIFEST.in | 2 +-
README.md | 4 ++--
docs/source/int_started.rst | 2 +-
.../icons/1024x1024/novelwriter.png | Bin
.../icons/1024x1024/x-novelwriter-project.png | Bin
.../icons/128x128/novelwriter.png | Bin
.../icons/128x128/x-novelwriter-project.png | Bin
{assets => setup}/icons/16x16/novelwriter.png | Bin
.../icons/16x16/x-novelwriter-project.png | Bin
.../icons/16x16@2x/novelwriter.png | Bin
.../icons/16x16@2x/x-novelwriter-project.png | Bin
.../icons/18x18@2x/novelwriter.png | Bin
.../icons/18x18@2x/x-novelwriter-project.png | Bin
{assets => setup}/icons/24x24/novelwriter.png | Bin
.../icons/24x24/x-novelwriter-project.png | Bin
.../icons/256x256/novelwriter.png | Bin
.../icons/256x256/x-novelwriter-project.png | Bin
{assets => setup}/icons/32x32/novelwriter.png | Bin
.../icons/32x32/x-novelwriter-project.png | Bin
.../icons/32x32@2x/novelwriter.png | Bin
.../icons/32x32@2x/x-novelwriter-project.png | Bin
{assets => setup}/icons/48x48/novelwriter.png | Bin
.../icons/48x48/x-novelwriter-project.png | Bin
.../icons/512x512/novelwriter.png | Bin
.../icons/512x512/x-novelwriter-project.png | Bin
{assets => setup}/icons/64x64/novelwriter.png | Bin
.../icons/64x64/x-novelwriter-project.png | Bin
{assets => setup}/icons/96x96/novelwriter.png | Bin
.../icons/96x96/x-novelwriter-project.png | Bin
{assets => setup}/icons/novelwriter.ico | Bin
{assets => setup}/icons/novelwriter.svg | 0
.../icons/x-novelwriter-project.svg | 0
{assets => setup}/installDebianUbuntu.sh | 18 +++++++++---------
.../mime/x-novelwriter-project.xml | 0
{assets => setup}/novelwriter.desktop | 0
35 files changed, 13 insertions(+), 13 deletions(-)
rename {assets => setup}/icons/1024x1024/novelwriter.png (100%)
rename {assets => setup}/icons/1024x1024/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/128x128/novelwriter.png (100%)
rename {assets => setup}/icons/128x128/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/16x16/novelwriter.png (100%)
rename {assets => setup}/icons/16x16/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/16x16@2x/novelwriter.png (100%)
rename {assets => setup}/icons/16x16@2x/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/18x18@2x/novelwriter.png (100%)
rename {assets => setup}/icons/18x18@2x/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/24x24/novelwriter.png (100%)
rename {assets => setup}/icons/24x24/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/256x256/novelwriter.png (100%)
rename {assets => setup}/icons/256x256/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/32x32/novelwriter.png (100%)
rename {assets => setup}/icons/32x32/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/32x32@2x/novelwriter.png (100%)
rename {assets => setup}/icons/32x32@2x/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/48x48/novelwriter.png (100%)
rename {assets => setup}/icons/48x48/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/512x512/novelwriter.png (100%)
rename {assets => setup}/icons/512x512/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/64x64/novelwriter.png (100%)
rename {assets => setup}/icons/64x64/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/96x96/novelwriter.png (100%)
rename {assets => setup}/icons/96x96/x-novelwriter-project.png (100%)
rename {assets => setup}/icons/novelwriter.ico (100%)
rename {assets => setup}/icons/novelwriter.svg (100%)
rename {assets => setup}/icons/x-novelwriter-project.svg (100%)
rename {assets => setup}/installDebianUbuntu.sh (54%)
rename {assets => setup}/mime/x-novelwriter-project.xml (100%)
rename {assets => setup}/novelwriter.desktop (100%)
diff --git a/MANIFEST.in b/MANIFEST.in
index 36a99646..b3004fc3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
include LICENSE.md
-recursive-include assets *
+recursive-include setup *
recursive-include nw/assets *
recursive-include sample *.nwx *.nwd
diff --git a/README.md b/README.md
index 46273c44..3b7835ed 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
[](https://pypi.org/project/novelWriter)
[](https://pypi.org/project/novelWriter)
-
+
novelWriter is a Markdown-like text editor designed for writing novels and larger projects of many
smaller plain text documents. It uses its own flavour of Markdown that supports a meta data syntax
@@ -81,7 +81,7 @@ You can also provide a path to a folder containing a novelWriter project as the
### Launcher and Icons
-In the root assets folder there are icons and scripts and a template for setting up a launcher on
+In the root setup folder there are icons and scripts and a template for setting up a launcher on
Gnome desktops. You may need to modify those scripts slightly, but as they are, they work on Debian
and Ubuntu. For other operating systems, please consult your operating system documentation for how
to make those. Feel free to submit more if you are able to make them.
diff --git a/docs/source/int_started.rst b/docs/source/int_started.rst
index e96a0285..0d0236c8 100644
--- a/docs/source/int_started.rst
+++ b/docs/source/int_started.rst
@@ -140,7 +140,7 @@ encountered. To list all options, run:
python novelWriter.py --help
-There are also a couple of install scripts in the assets folder which will assist in setting up a
+There are also a couple of install scripts in the setup folder which will assist in setting up a
launch icon and the novelWriter project file mimetype for Gnome desktops on Linux. Currently,
there's one script for Debian and one for Ubuntu.
diff --git a/assets/icons/1024x1024/novelwriter.png b/setup/icons/1024x1024/novelwriter.png
similarity index 100%
rename from assets/icons/1024x1024/novelwriter.png
rename to setup/icons/1024x1024/novelwriter.png
diff --git a/assets/icons/1024x1024/x-novelwriter-project.png b/setup/icons/1024x1024/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/1024x1024/x-novelwriter-project.png
rename to setup/icons/1024x1024/x-novelwriter-project.png
diff --git a/assets/icons/128x128/novelwriter.png b/setup/icons/128x128/novelwriter.png
similarity index 100%
rename from assets/icons/128x128/novelwriter.png
rename to setup/icons/128x128/novelwriter.png
diff --git a/assets/icons/128x128/x-novelwriter-project.png b/setup/icons/128x128/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/128x128/x-novelwriter-project.png
rename to setup/icons/128x128/x-novelwriter-project.png
diff --git a/assets/icons/16x16/novelwriter.png b/setup/icons/16x16/novelwriter.png
similarity index 100%
rename from assets/icons/16x16/novelwriter.png
rename to setup/icons/16x16/novelwriter.png
diff --git a/assets/icons/16x16/x-novelwriter-project.png b/setup/icons/16x16/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/16x16/x-novelwriter-project.png
rename to setup/icons/16x16/x-novelwriter-project.png
diff --git a/assets/icons/16x16@2x/novelwriter.png b/setup/icons/16x16@2x/novelwriter.png
similarity index 100%
rename from assets/icons/16x16@2x/novelwriter.png
rename to setup/icons/16x16@2x/novelwriter.png
diff --git a/assets/icons/16x16@2x/x-novelwriter-project.png b/setup/icons/16x16@2x/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/16x16@2x/x-novelwriter-project.png
rename to setup/icons/16x16@2x/x-novelwriter-project.png
diff --git a/assets/icons/18x18@2x/novelwriter.png b/setup/icons/18x18@2x/novelwriter.png
similarity index 100%
rename from assets/icons/18x18@2x/novelwriter.png
rename to setup/icons/18x18@2x/novelwriter.png
diff --git a/assets/icons/18x18@2x/x-novelwriter-project.png b/setup/icons/18x18@2x/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/18x18@2x/x-novelwriter-project.png
rename to setup/icons/18x18@2x/x-novelwriter-project.png
diff --git a/assets/icons/24x24/novelwriter.png b/setup/icons/24x24/novelwriter.png
similarity index 100%
rename from assets/icons/24x24/novelwriter.png
rename to setup/icons/24x24/novelwriter.png
diff --git a/assets/icons/24x24/x-novelwriter-project.png b/setup/icons/24x24/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/24x24/x-novelwriter-project.png
rename to setup/icons/24x24/x-novelwriter-project.png
diff --git a/assets/icons/256x256/novelwriter.png b/setup/icons/256x256/novelwriter.png
similarity index 100%
rename from assets/icons/256x256/novelwriter.png
rename to setup/icons/256x256/novelwriter.png
diff --git a/assets/icons/256x256/x-novelwriter-project.png b/setup/icons/256x256/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/256x256/x-novelwriter-project.png
rename to setup/icons/256x256/x-novelwriter-project.png
diff --git a/assets/icons/32x32/novelwriter.png b/setup/icons/32x32/novelwriter.png
similarity index 100%
rename from assets/icons/32x32/novelwriter.png
rename to setup/icons/32x32/novelwriter.png
diff --git a/assets/icons/32x32/x-novelwriter-project.png b/setup/icons/32x32/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/32x32/x-novelwriter-project.png
rename to setup/icons/32x32/x-novelwriter-project.png
diff --git a/assets/icons/32x32@2x/novelwriter.png b/setup/icons/32x32@2x/novelwriter.png
similarity index 100%
rename from assets/icons/32x32@2x/novelwriter.png
rename to setup/icons/32x32@2x/novelwriter.png
diff --git a/assets/icons/32x32@2x/x-novelwriter-project.png b/setup/icons/32x32@2x/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/32x32@2x/x-novelwriter-project.png
rename to setup/icons/32x32@2x/x-novelwriter-project.png
diff --git a/assets/icons/48x48/novelwriter.png b/setup/icons/48x48/novelwriter.png
similarity index 100%
rename from assets/icons/48x48/novelwriter.png
rename to setup/icons/48x48/novelwriter.png
diff --git a/assets/icons/48x48/x-novelwriter-project.png b/setup/icons/48x48/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/48x48/x-novelwriter-project.png
rename to setup/icons/48x48/x-novelwriter-project.png
diff --git a/assets/icons/512x512/novelwriter.png b/setup/icons/512x512/novelwriter.png
similarity index 100%
rename from assets/icons/512x512/novelwriter.png
rename to setup/icons/512x512/novelwriter.png
diff --git a/assets/icons/512x512/x-novelwriter-project.png b/setup/icons/512x512/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/512x512/x-novelwriter-project.png
rename to setup/icons/512x512/x-novelwriter-project.png
diff --git a/assets/icons/64x64/novelwriter.png b/setup/icons/64x64/novelwriter.png
similarity index 100%
rename from assets/icons/64x64/novelwriter.png
rename to setup/icons/64x64/novelwriter.png
diff --git a/assets/icons/64x64/x-novelwriter-project.png b/setup/icons/64x64/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/64x64/x-novelwriter-project.png
rename to setup/icons/64x64/x-novelwriter-project.png
diff --git a/assets/icons/96x96/novelwriter.png b/setup/icons/96x96/novelwriter.png
similarity index 100%
rename from assets/icons/96x96/novelwriter.png
rename to setup/icons/96x96/novelwriter.png
diff --git a/assets/icons/96x96/x-novelwriter-project.png b/setup/icons/96x96/x-novelwriter-project.png
similarity index 100%
rename from assets/icons/96x96/x-novelwriter-project.png
rename to setup/icons/96x96/x-novelwriter-project.png
diff --git a/assets/icons/novelwriter.ico b/setup/icons/novelwriter.ico
similarity index 100%
rename from assets/icons/novelwriter.ico
rename to setup/icons/novelwriter.ico
diff --git a/assets/icons/novelwriter.svg b/setup/icons/novelwriter.svg
similarity index 100%
rename from assets/icons/novelwriter.svg
rename to setup/icons/novelwriter.svg
diff --git a/assets/icons/x-novelwriter-project.svg b/setup/icons/x-novelwriter-project.svg
similarity index 100%
rename from assets/icons/x-novelwriter-project.svg
rename to setup/icons/x-novelwriter-project.svg
diff --git a/assets/installDebianUbuntu.sh b/setup/installDebianUbuntu.sh
similarity index 54%
rename from assets/installDebianUbuntu.sh
rename to setup/installDebianUbuntu.sh
index 91e78409..c47f4d26 100755
--- a/assets/installDebianUbuntu.sh
+++ b/setup/installDebianUbuntu.sh
@@ -5,7 +5,7 @@ cd ..
EXEC=$(pwd)/novelWriter.py
EXEC=$(echo $EXEC | sed 's_/_\\/_g')
-sed "s/%%exec%%/$EXEC/g" assets/novelwriter.desktop > /usr/share/applications/novelwriter.desktop
+sed "s/%%exec%%/$EXEC/g" setup/novelwriter.desktop > /usr/share/applications/novelwriter.desktop
if [ ! -d /usr/share/icons/hicolor/24x24/apps ]; then
mkdir -pv /usr/share/icons/hicolor/24x24/apps
@@ -29,14 +29,14 @@ if [ ! -d /usr/share/icons/hicolor/scalable/mimetypes ]; then
mkdir -pv /usr/share/icons/hicolor/scalable/mimetypes
fi
-cp -v assets/icons/24x24/novelwriter.png /usr/share/icons/hicolor/24x24/apps/
-cp -v assets/icons/48x48/novelwriter.png /usr/share/icons/hicolor/48x48/apps/
-cp -v assets/icons/96x96/novelwriter.png /usr/share/icons/hicolor/96x96/apps/
-cp -v assets/icons/256x256/novelwriter.png /usr/share/icons/hicolor/256x256/apps/
-cp -v assets/icons/512x512/novelwriter.png /usr/share/icons/hicolor/512x512/apps/
-cp -v assets/icons/novelwriter.svg /usr/share/icons/hicolor/scalable/apps/
-cp -v assets/icons/x-novelwriter-project.svg /usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg
-cp -v assets/mime/x-novelwriter-project.xml /usr/share/mime/packages/
+cp -v setup/icons/24x24/novelwriter.png /usr/share/icons/hicolor/24x24/apps/
+cp -v setup/icons/48x48/novelwriter.png /usr/share/icons/hicolor/48x48/apps/
+cp -v setup/icons/96x96/novelwriter.png /usr/share/icons/hicolor/96x96/apps/
+cp -v setup/icons/256x256/novelwriter.png /usr/share/icons/hicolor/256x256/apps/
+cp -v setup/icons/512x512/novelwriter.png /usr/share/icons/hicolor/512x512/apps/
+cp -v setup/icons/novelwriter.svg /usr/share/icons/hicolor/scalable/apps/
+cp -v setup/icons/x-novelwriter-project.svg /usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg
+cp -v setup/mime/x-novelwriter-project.xml /usr/share/mime/packages/
update-mime-database /usr/share/mime/
update-icon-caches /usr/share/icons/*
diff --git a/assets/mime/x-novelwriter-project.xml b/setup/mime/x-novelwriter-project.xml
similarity index 100%
rename from assets/mime/x-novelwriter-project.xml
rename to setup/mime/x-novelwriter-project.xml
diff --git a/assets/novelwriter.desktop b/setup/novelwriter.desktop
similarity index 100%
rename from assets/novelwriter.desktop
rename to setup/novelwriter.desktop
From 403066a83f07d573823811adae3f925f3472260b Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 16 Oct 2020 18:53:29 +0200
Subject: [PATCH 11/28] Moe settings from setup.py to setup.cfg, and add
pyproject.toml
---
pyproject.toml | 3 +++
setup.cfg | 45 ++++++++++++++++++++++++++++++++++++++++++++-
setup.py | 48 +-----------------------------------------------
3 files changed, 48 insertions(+), 48 deletions(-)
create mode 100644 pyproject.toml
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..9787c3bd
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
index ff0117c5..f9b19c12 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,49 @@
[metadata]
-license_files = LICENSE.md
+name = novelWriter
version = attr: nw.__version__
+author = Veronica Berglyd Olsen
+author_email = code@vkbo.net
+description = A markdown-like document editor for writing novels
+url = https://novelwriter.io
+long_description = file: README.md
+long_description_content_type = text/markdown
+license_files = LICENSE.md
+license = GNU General Public License v3
+classifiers =
+ Programming Language :: Python :: 3 :: Only
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Programming Language :: Python :: 3.9
+ Programming Language :: Python :: Implementation :: CPython
+ License :: OSI Approved :: GNU General Public License v3 (GPLv3)
+ Development Status :: 4 - Beta
+ Operating System :: OS Independent
+ Intended Audience :: End Users/Desktop
+ Natural Language :: English
+ Topic :: Text Editors
+python_requires = >=3.6
+install_requires =
+ pyqt5>=5.2.1
+ lxml>=4.2.0
+ pyenchant>=3.0.0
+project_urls =
+ Bug Tracker = https://github.com/vkbo/novelWriter/issues
+ Documentation = https://github.com/vkbo/novelWriter/issues
+ Source Code = https://github.com/vkbo/novelWriter
+
+[options]
+include_package_data = True
+packages = find:
+
+[options.packages.find]
+exclude = docs, tests, sample
+
+[options.entry_points]
+console_script =
+ novelWriter-cli = nw:main
+gui_scripts =
+ novelWriter = nw:main
[bdist_wheel]
universal = 0
diff --git a/setup.py b/setup.py
index 6b10a84e..ed17b904 100755
--- a/setup.py
+++ b/setup.py
@@ -113,50 +113,4 @@ if len(sys.argv) == 1:
# Build the Package
##
-# Read content from files
-with open("README.md", "r") as inFile:
- longDescription = inFile.read()
-
-setuptools.setup(
- name = "novelWriter",
- # version = __version__, # Set in setup.cfg
- author = "Veronica Berglyd Olsen",
- author_email = "code@vkbo.net",
- description = "A markdown-like document editor for writing novels",
- long_description = longDescription,
- long_description_content_type = "text/markdown",
- license = "GNU General Public License v3",
- url = "https://novelwriter.io",
- entry_points = {
- "console_scripts" : ["novelWriter-cli=nw:main"],
- "gui_scripts" : ["novelWriter=nw:main"],
- },
- packages = setuptools.find_packages(exclude=["docs", "tests", "sample"]),
- include_package_data = True,
- package_data = {"": ["*.conf"]},
- project_urls = {
- "Bug Tracker": "https://github.com/vkbo/novelWriter/issues",
- "Documentation": "https://github.com/vkbo/novelWriter/issues",
- "Source Code": "https://github.com/vkbo/novelWriter",
- },
- classifiers = [
- "Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: Implementation :: CPython",
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
- "Development Status :: 4 - Beta",
- "Operating System :: OS Independent",
- "Intended Audience :: End Users/Desktop",
- "Natural Language :: English",
- "Topic :: Text Editors",
- ],
- python_requires = ">=3.6",
- install_requires = [
- "pyqt5>=5.2.1",
- "lxml>=4.2.0",
- "pyenchant>=3.0.0",
- ],
-)
+setuptools.setup()
From 70086f5ca65dde4bafe39ff1d8d4bfe8710ddd5d Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 00:58:48 +0200
Subject: [PATCH 12/28] Added python and iss script for making setup.exe
releases on Windows
---
make_windows.py | 177 ++++++++++++++++++++++++++++++++++++++++++++
setup.cfg | 2 +-
setup/win_setup.iss | 51 +++++++++++++
3 files changed, 229 insertions(+), 1 deletion(-)
create mode 100644 make_windows.py
create mode 100644 setup/win_setup.iss
diff --git a/make_windows.py b/make_windows.py
new file mode 100644
index 00000000..c64b79d0
--- /dev/null
+++ b/make_windows.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import nw
+import os
+import sys
+import getopt
+import subprocess
+
+# Defaults
+buildWindowed = True
+runPip = False
+oneFile = False
+makeSetup = False
+innoSetup = None
+
+# Parse Options
+shortOpt = "hd"
+longOpt = [
+ "help",
+ "debug",
+ "pip",
+ "onefile",
+ "setup",
+ "inno=",
+]
+helpMsg = (
+ "\n"
+ "novelWriter Install Script\n"
+ "\n"
+ "Usage:\n"
+ " -h, --help Print this message.\n"
+ " --pip Install dependecies first.\n"
+ " --onefile Create a single executable file.\n"
+ " --setup Make Inno Setup file.\n"
+ " --inno= Path to the Inoo Setup exec.\n"
+ " -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n"
+ " run it from command line with the debug options. Please check the\n"
+ " novelWriter --help output for details.\n"
+)
+
+try:
+ inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt)
+except getopt.GetoptError:
+ print(helpMsg)
+ sys.exit(1)
+
+for inOpt, inArg in inOpts:
+ if inOpt in ("-h", "--help"):
+ print(helpMsg)
+ sys.exit(0)
+ elif inOpt in ("-d", "--debug"):
+ buildWindowed = False
+ elif inOpt == "--pip":
+ runPip = True
+ elif inOpt == "--onefile":
+ oneFile = True
+ elif inOpt == "--setup":
+ makeSetup = True
+ elif inOpt == "--inno":
+ innoSetup = inArg
+
+# Run pip
+if runPip:
+ print("")
+ print("###########################")
+ print(" Installing Dependencies")
+ print("###########################")
+ print("")
+ try:
+ subprocess.call([
+ sys.executable, "-m",
+ "pip", "install", "--user", "--upgrade", "pip"
+ ])
+ subprocess.call([
+ sys.executable, "-m",
+ "pip", "install", "--user", "--upgrade", "pyinstaller"
+ ])
+ subprocess.call([
+ sys.executable, "-m",
+ "pip", "install", "--user", "--upgrade", "-r", "requirements.txt"
+ ])
+ except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
+
+# Run pyinstaller
+print("")
+print("#######################")
+print(" Running PyInstaller")
+print("#######################")
+print("")
+instOpt = [
+ "--name=novelWriter",
+ "--clean",
+ "--add-data=%s;%s" % (os.path.join("nw", "assets"), "assets"),
+ "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
+ "--exclude-module=PyQt5.QtQml",
+ "--exclude-module=PyQt5.QtBluetooth",
+ "--exclude-module=PyQt5.QtDBus",
+ "--exclude-module=PyQt5.QtMultimedia",
+ "--exclude-module=PyQt5.QtMultimediaWidgets",
+ "--exclude-module=PyQt5.QtNetwork",
+ "--exclude-module=PyQt5.QtNetworkAuth",
+ "--exclude-module=PyQt5.QtNfc",
+ "--exclude-module=PyQt5.QtQuick",
+ "--exclude-module=PyQt5.QtQuickWidgets",
+ "--exclude-module=PyQt5.QtRemoteObjects",
+ "--exclude-module=PyQt5.QtSensors",
+ "--exclude-module=PyQt5.QtSerialPort",
+ "--exclude-module=PyQt5.QtSql",
+]
+if buildWindowed:
+ instOpt.append("--windowed")
+if oneFile and not makeSetup:
+ instOpt.append("--onefile")
+else:
+ instOpt.append("--onedir")
+
+instOpt.append("novelWriter.py")
+
+import PyInstaller.__main__ # noqa: E402
+PyInstaller.__main__.run(instOpt)
+
+if not oneFile:
+ delIfExists = [
+ "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll",
+ "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll",
+ "Qt5Quick3DRuntimeRender.dll", "Qt5Quick3DUtils.dll", "Qt5Sql.dll"
+ ]
+ distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
+ for delFile in delIfExists:
+ delPath = os.path.join(distDir, delFile)
+ if os.path.isfile(delPath):
+ print("Deleting file: %s" % delPath)
+ os.unlink(delPath)
+
+print("")
+print("Build Finished")
+print("")
+print("If everything went well, the novelWriter executable should be in the folder named 'dist'")
+print("")
+
+if makeSetup:
+ print("")
+ print("######################")
+ print(" Running Inno Setup")
+ print("######################")
+ print("")
+ if innoSetup is None:
+ innoSetup = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
+ if not os.path.isfile(innoSetup):
+ print("ERROR: Cannot fine Inno Setup's ISCC.exe file.")
+ print(" Looked in: %s" % innoSetup)
+ print(" Please provide a path with the --inno= option.")
+ sys.exit(1)
+
+ # Read the iss template
+ issData = ""
+ with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile:
+ issData = inFile.read()
+
+ issData = issData.replace(r"%%version%%", nw.__version__)
+ issData = issData.replace(r"%%dir%%", os.getcwd())
+
+ with open("setup.iss", mode="w+") as outFile:
+ outFile.write(issData)
+
+ try:
+ subprocess.call(
+ [innoSetup, "setup.iss"]
+ )
+ except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
diff --git a/setup.cfg b/setup.cfg
index f9b19c12..ad1c6ddb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -7,7 +7,7 @@ description = A markdown-like document editor for writing novels
url = https://novelwriter.io
long_description = file: README.md
long_description_content_type = text/markdown
-license_files = LICENSE.md
+license_file = LICENSE.md
license = GNU General Public License v3
classifiers =
Programming Language :: Python :: 3 :: Only
diff --git a/setup/win_setup.iss b/setup/win_setup.iss
new file mode 100644
index 00000000..6491b317
--- /dev/null
+++ b/setup/win_setup.iss
@@ -0,0 +1,51 @@
+; Script generated by the Inno Setup Script Wizard.
+; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+
+#define nwAppDir "%%dir%%\dist"
+#define nwAppName "novelWriter"
+#define nwAppVersion "%%version%%"
+#define nwAppPublisher "novelWriter"
+#define nwAppURL "http://novelWriter.io"
+#define nwAppExeName "novelWriter.exe"
+
+[Setup]
+; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
+; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
+AppId={{459A75D0-951F-4932-9809-6002EC8E733E}
+AppName={#nwAppName}
+AppVersion={#nwAppVersion}
+AppVerName={#nwAppName} {#nwAppVersion}
+AppPublisher={#nwAppPublisher}
+AppPublisherURL={#nwAppURL}
+AppSupportURL={#nwAppURL}
+AppUpdatesURL={#nwAppURL}
+DefaultDirName={autopf}\{#nwAppName}
+DisableProgramGroupPage=yes
+; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
+UsedUserAreasWarning=no
+; Uncomment the following line to run in non administrative install mode (install for current user only.)
+;PrivilegesRequired=lowest
+PrivilegesRequiredOverridesAllowed=dialog
+OutputDir={#nwAppDir}
+OutputBaseFilename=setup-novelwriter-{#nwAppVersion}
+Compression=lzma
+SolidCompression=yes
+WizardStyle=modern
+
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
+
+[Files]
+Source: "{#nwAppDir}\novelWriter\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
+
+[Icons]
+Name: "{autoprograms}\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"
+Name: "{autodesktop}\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"; Tasks: desktopicon
+Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"; Tasks: quicklaunchicon
+
+[Run]
+Filename: "{app}\{#nwAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(nwAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
From 115cef3ffb18c5fe601ddbdcdd1505207037bf20 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 01:05:49 +0200
Subject: [PATCH 13/28] Added more comments to windows script
---
make_windows.py | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/make_windows.py b/make_windows.py
index c64b79d0..27e8c112 100644
--- a/make_windows.py
+++ b/make_windows.py
@@ -1,5 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+"""
+This script will either build:
+ * A single file executable named dist/novelWriter.exe. This is a quite
+ slow option, and the file is fairly big. Option --onefile
+ * A single directory named dist/novelWriter with a novelWriter.exe, and
+ all dependecies included. This is the default.
+ * The latter can be combined with a build stage of a setup.exe file
+ named setup-novelwriter-.exe. Option --setup.
+
+In addition, providing the --pip flag will cause the script to try to
+install all dependencies needed for runing the build, and for running
+novelWriter itself.
+"""
import nw
import os
@@ -26,14 +39,14 @@ longOpt = [
]
helpMsg = (
"\n"
- "novelWriter Install Script\n"
+ "novelWriter Install Script for Windows\n"
"\n"
"Usage:\n"
" -h, --help Print this message.\n"
" --pip Install dependecies first.\n"
" --onefile Create a single executable file.\n"
" --setup Make Inno Setup file.\n"
- " --inno= Path to the Inoo Setup exec.\n"
+ " --inno= Path to the Inno Setup exec.\n"
" -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n"
" run it from command line with the debug options. Please check the\n"
" novelWriter --help output for details.\n"
@@ -111,8 +124,10 @@ instOpt = [
"--exclude-module=PyQt5.QtSerialPort",
"--exclude-module=PyQt5.QtSql",
]
+
if buildWindowed:
instOpt.append("--windowed")
+
if oneFile and not makeSetup:
instOpt.append("--onefile")
else:
@@ -124,6 +139,7 @@ import PyInstaller.__main__ # noqa: E402
PyInstaller.__main__.run(instOpt)
if not oneFile:
+ # These dll files are not nee3ded, and take up a fair bit of space.
delIfExists = [
"Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll",
"Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll",
@@ -168,9 +184,7 @@ if makeSetup:
outFile.write(issData)
try:
- subprocess.call(
- [innoSetup, "setup.iss"]
- )
+ subprocess.call([innoSetup, "setup.iss"])
except Exception as e:
print("Failed with error:")
print(str(e))
From 0665b9b0321910752e30eba3a781543178e31b38 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 01:30:13 +0200
Subject: [PATCH 14/28] Added temp iss file to gitignore, and fiex a bug in
windows make
---
.gitignore | 1 +
make_windows.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 57dc50a9..830a409a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
/deploy/
*.spec
*.egg-info
+setup.iss
# Documentation
/docs/build/
diff --git a/make_windows.py b/make_windows.py
index 27e8c112..cc75f519 100644
--- a/make_windows.py
+++ b/make_windows.py
@@ -14,7 +14,6 @@ install all dependencies needed for runing the build, and for running
novelWriter itself.
"""
-import nw
import os
import sys
import getopt
@@ -177,6 +176,7 @@ if makeSetup:
with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile:
issData = inFile.read()
+ import nw # noqa: E402
issData = issData.replace(r"%%version%%", nw.__version__)
issData = issData.replace(r"%%dir%%", os.getcwd())
From ede9d034c797b393867ea86a29464469aeaf42c1 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 01:43:47 +0200
Subject: [PATCH 15/28] Make sample.zip before pyinstaller runs, and make sure
the make_windows script is windows only
---
make_windows.py | 395 +++++++++++++++++++++++++-----------------------
1 file changed, 204 insertions(+), 191 deletions(-)
mode change 100644 => 100755 make_windows.py
diff --git a/make_windows.py b/make_windows.py
old mode 100644
new mode 100755
index cc75f519..d5b938c0
--- a/make_windows.py
+++ b/make_windows.py
@@ -1,191 +1,204 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-This script will either build:
- * A single file executable named dist/novelWriter.exe. This is a quite
- slow option, and the file is fairly big. Option --onefile
- * A single directory named dist/novelWriter with a novelWriter.exe, and
- all dependecies included. This is the default.
- * The latter can be combined with a build stage of a setup.exe file
- named setup-novelwriter-.exe. Option --setup.
-
-In addition, providing the --pip flag will cause the script to try to
-install all dependencies needed for runing the build, and for running
-novelWriter itself.
-"""
-
-import os
-import sys
-import getopt
-import subprocess
-
-# Defaults
-buildWindowed = True
-runPip = False
-oneFile = False
-makeSetup = False
-innoSetup = None
-
-# Parse Options
-shortOpt = "hd"
-longOpt = [
- "help",
- "debug",
- "pip",
- "onefile",
- "setup",
- "inno=",
-]
-helpMsg = (
- "\n"
- "novelWriter Install Script for Windows\n"
- "\n"
- "Usage:\n"
- " -h, --help Print this message.\n"
- " --pip Install dependecies first.\n"
- " --onefile Create a single executable file.\n"
- " --setup Make Inno Setup file.\n"
- " --inno= Path to the Inno Setup exec.\n"
- " -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n"
- " run it from command line with the debug options. Please check the\n"
- " novelWriter --help output for details.\n"
-)
-
-try:
- inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt)
-except getopt.GetoptError:
- print(helpMsg)
- sys.exit(1)
-
-for inOpt, inArg in inOpts:
- if inOpt in ("-h", "--help"):
- print(helpMsg)
- sys.exit(0)
- elif inOpt in ("-d", "--debug"):
- buildWindowed = False
- elif inOpt == "--pip":
- runPip = True
- elif inOpt == "--onefile":
- oneFile = True
- elif inOpt == "--setup":
- makeSetup = True
- elif inOpt == "--inno":
- innoSetup = inArg
-
-# Run pip
-if runPip:
- print("")
- print("###########################")
- print(" Installing Dependencies")
- print("###########################")
- print("")
- try:
- subprocess.call([
- sys.executable, "-m",
- "pip", "install", "--user", "--upgrade", "pip"
- ])
- subprocess.call([
- sys.executable, "-m",
- "pip", "install", "--user", "--upgrade", "pyinstaller"
- ])
- subprocess.call([
- sys.executable, "-m",
- "pip", "install", "--user", "--upgrade", "-r", "requirements.txt"
- ])
- except Exception as e:
- print("Failed with error:")
- print(str(e))
- sys.exit(1)
-
-# Run pyinstaller
-print("")
-print("#######################")
-print(" Running PyInstaller")
-print("#######################")
-print("")
-instOpt = [
- "--name=novelWriter",
- "--clean",
- "--add-data=%s;%s" % (os.path.join("nw", "assets"), "assets"),
- "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
- "--exclude-module=PyQt5.QtQml",
- "--exclude-module=PyQt5.QtBluetooth",
- "--exclude-module=PyQt5.QtDBus",
- "--exclude-module=PyQt5.QtMultimedia",
- "--exclude-module=PyQt5.QtMultimediaWidgets",
- "--exclude-module=PyQt5.QtNetwork",
- "--exclude-module=PyQt5.QtNetworkAuth",
- "--exclude-module=PyQt5.QtNfc",
- "--exclude-module=PyQt5.QtQuick",
- "--exclude-module=PyQt5.QtQuickWidgets",
- "--exclude-module=PyQt5.QtRemoteObjects",
- "--exclude-module=PyQt5.QtSensors",
- "--exclude-module=PyQt5.QtSerialPort",
- "--exclude-module=PyQt5.QtSql",
-]
-
-if buildWindowed:
- instOpt.append("--windowed")
-
-if oneFile and not makeSetup:
- instOpt.append("--onefile")
-else:
- instOpt.append("--onedir")
-
-instOpt.append("novelWriter.py")
-
-import PyInstaller.__main__ # noqa: E402
-PyInstaller.__main__.run(instOpt)
-
-if not oneFile:
- # These dll files are not nee3ded, and take up a fair bit of space.
- delIfExists = [
- "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll",
- "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll",
- "Qt5Quick3DRuntimeRender.dll", "Qt5Quick3DUtils.dll", "Qt5Sql.dll"
- ]
- distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
- for delFile in delIfExists:
- delPath = os.path.join(distDir, delFile)
- if os.path.isfile(delPath):
- print("Deleting file: %s" % delPath)
- os.unlink(delPath)
-
-print("")
-print("Build Finished")
-print("")
-print("If everything went well, the novelWriter executable should be in the folder named 'dist'")
-print("")
-
-if makeSetup:
- print("")
- print("######################")
- print(" Running Inno Setup")
- print("######################")
- print("")
- if innoSetup is None:
- innoSetup = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
- if not os.path.isfile(innoSetup):
- print("ERROR: Cannot fine Inno Setup's ISCC.exe file.")
- print(" Looked in: %s" % innoSetup)
- print(" Please provide a path with the --inno= option.")
- sys.exit(1)
-
- # Read the iss template
- issData = ""
- with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile:
- issData = inFile.read()
-
- import nw # noqa: E402
- issData = issData.replace(r"%%version%%", nw.__version__)
- issData = issData.replace(r"%%dir%%", os.getcwd())
-
- with open("setup.iss", mode="w+") as outFile:
- outFile.write(issData)
-
- try:
- subprocess.call([innoSetup, "setup.iss"])
- except Exception as e:
- print("Failed with error:")
- print(str(e))
- sys.exit(1)
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+This script will either build:
+ * A single file executable named dist/novelWriter.exe. This is a quite
+ slow option, and the file is fairly big. Option --onefile
+ * A single directory named dist/novelWriter with a novelWriter.exe, and
+ all dependecies included. This is the default.
+ * The latter can be combined with a build stage of a setup.exe file
+ named setup-novelwriter-.exe. Option --setup.
+
+In addition, providing the --pip flag will cause the script to try to
+install all dependencies needed for runing the build, and for running
+novelWriter itself.
+"""
+
+import os
+import sys
+import getopt
+import subprocess
+
+if not sys.platform.startswith("win32"):
+ print("ERROR: This script is intended for Windows only.")
+ sys.exit(1)
+
+# Defaults
+buildWindowed = True
+runPip = False
+oneFile = False
+makeSetup = False
+innoSetup = None
+
+# Parse Options
+shortOpt = "hd"
+longOpt = [
+ "help",
+ "debug",
+ "pip",
+ "onefile",
+ "setup",
+ "inno=",
+]
+helpMsg = (
+ "\n"
+ "novelWriter Install Script for Windows\n"
+ "\n"
+ "Usage:\n"
+ " -h, --help Print this message.\n"
+ " --pip Install dependecies first.\n"
+ " --onefile Create a single executable file.\n"
+ " --setup Make Inno Setup file.\n"
+ " --inno= Path to the Inno Setup exec.\n"
+ " -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n"
+ " run it from command line with the debug options. Please check the\n"
+ " novelWriter --help output for details.\n"
+)
+
+try:
+ inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt)
+except getopt.GetoptError:
+ print(helpMsg)
+ sys.exit(1)
+
+for inOpt, inArg in inOpts:
+ if inOpt in ("-h", "--help"):
+ print(helpMsg)
+ sys.exit(0)
+ elif inOpt in ("-d", "--debug"):
+ buildWindowed = False
+ elif inOpt == "--pip":
+ runPip = True
+ elif inOpt == "--onefile":
+ oneFile = True
+ elif inOpt == "--setup":
+ makeSetup = True
+ elif inOpt == "--inno":
+ innoSetup = inArg
+
+# Run pip
+if runPip:
+ print("")
+ print("###########################")
+ print(" Installing Dependencies")
+ print("###########################")
+ print("")
+ try:
+ subprocess.call([
+ sys.executable, "-m",
+ "pip", "install", "--user", "--upgrade", "pip"
+ ])
+ subprocess.call([
+ sys.executable, "-m",
+ "pip", "install", "--user", "--upgrade", "pyinstaller"
+ ])
+ subprocess.call([
+ sys.executable, "-m",
+ "pip", "install", "--user", "--upgrade", "-r", "requirements.txt"
+ ])
+ except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
+
+# Run pyinstaller
+print("")
+print("#######################")
+print(" Running PyInstaller")
+print("#######################")
+print("")
+instOpt = [
+ "--name=novelWriter",
+ "--clean",
+ "--add-data=%s;%s" % (os.path.join("nw", "assets"), "assets"),
+ "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
+ "--exclude-module=PyQt5.QtQml",
+ "--exclude-module=PyQt5.QtBluetooth",
+ "--exclude-module=PyQt5.QtDBus",
+ "--exclude-module=PyQt5.QtMultimedia",
+ "--exclude-module=PyQt5.QtMultimediaWidgets",
+ "--exclude-module=PyQt5.QtNetwork",
+ "--exclude-module=PyQt5.QtNetworkAuth",
+ "--exclude-module=PyQt5.QtNfc",
+ "--exclude-module=PyQt5.QtQuick",
+ "--exclude-module=PyQt5.QtQuickWidgets",
+ "--exclude-module=PyQt5.QtRemoteObjects",
+ "--exclude-module=PyQt5.QtSensors",
+ "--exclude-module=PyQt5.QtSerialPort",
+ "--exclude-module=PyQt5.QtSql",
+]
+
+if buildWindowed:
+ instOpt.append("--windowed")
+
+if oneFile and not makeSetup:
+ instOpt.append("--onefile")
+else:
+ instOpt.append("--onedir")
+
+instOpt.append("novelWriter.py")
+
+# Make sample.zip first
+print("Building sample.zip")
+try:
+ subprocess.call([sys.executable, "setup.py" "sample"])
+except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
+
+import PyInstaller.__main__ # noqa: E402
+PyInstaller.__main__.run(instOpt)
+
+if not oneFile:
+ # These dll files are not nee3ded, and take up a fair bit of space.
+ delIfExists = [
+ "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll",
+ "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll",
+ "Qt5Quick3DRuntimeRender.dll", "Qt5Quick3DUtils.dll", "Qt5Sql.dll"
+ ]
+ distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
+ for delFile in delIfExists:
+ delPath = os.path.join(distDir, delFile)
+ if os.path.isfile(delPath):
+ print("Deleting file: %s" % delPath)
+ os.unlink(delPath)
+
+print("")
+print("Build Finished")
+print("")
+print("If everything went well, the novelWriter executable should be in the folder named 'dist'")
+print("")
+
+if makeSetup:
+ print("")
+ print("######################")
+ print(" Running Inno Setup")
+ print("######################")
+ print("")
+ if innoSetup is None:
+ innoSetup = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
+ if not os.path.isfile(innoSetup):
+ print("ERROR: Cannot fine Inno Setup's ISCC.exe file.")
+ print(" Looked in: %s" % innoSetup)
+ print(" Please provide a path with the --inno= option.")
+ sys.exit(1)
+
+ # Read the iss template
+ issData = ""
+ with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile:
+ issData = inFile.read()
+
+ import nw # noqa: E402
+ issData = issData.replace(r"%%version%%", nw.__version__)
+ issData = issData.replace(r"%%dir%%", os.getcwd())
+
+ with open("setup.iss", mode="w+") as outFile:
+ outFile.write(issData)
+
+ try:
+ subprocess.call([innoSetup, "setup.iss"])
+ except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
From a5201d79cbbba375b824d3d11d00c97db63bb2c3 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 01:46:50 +0200
Subject: [PATCH 16/28] Fix typo
---
make_windows.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/make_windows.py b/make_windows.py
index d5b938c0..674fb023 100755
--- a/make_windows.py
+++ b/make_windows.py
@@ -141,7 +141,7 @@ instOpt.append("novelWriter.py")
# Make sample.zip first
print("Building sample.zip")
try:
- subprocess.call([sys.executable, "setup.py" "sample"])
+ subprocess.call([sys.executable, "setup.py", "sample"])
except Exception as e:
print("Failed with error:")
print(str(e))
From 3b63dd49dc4acf374c210f29b73e50b9cf09b272 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 13:03:32 +0200
Subject: [PATCH 17/28] Cleanup of the setup script
---
docs/source/conf.py | 1 -
setup.py | 93 +++++++++++++++++++++++++++++----------------
2 files changed, 60 insertions(+), 34 deletions(-)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index b72b8db3..a76fce4c 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -12,7 +12,6 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
-# import os
# import sys
# sys.path.insert(0, os.path.abspath("."))
import os
diff --git a/setup.py b/setup.py
index ed17b904..403a71f2 100755
--- a/setup.py
+++ b/setup.py
@@ -4,27 +4,22 @@ import sys
import subprocess
import setuptools
-##
-# Build the Package
-##
+# =========================================================================== #
+# Qt Assistant Documentation Builder
+# =========================================================================== #
-buildDocs = False
-buildSample = False
+def buildQtDocs():
+ """This function will build the documentation as a Qt help file. The
+ file is then copied into the nw/assets/help directory and can be
+ included in builds.
-if "qthelp" in sys.argv:
- buildDocs = True
- sys.argv.remove("qthelp")
-
-if "sample" in sys.argv:
- buildSample = True
- sys.argv.remove("sample")
-
-##
-# Qt Assistant Documentation
-##
-
-if buildDocs:
+ Depends on packages:
+ * pip install sphinx
+ * pip install sphinx-rtd-theme
+ * pip install sphinxcontrib-qthelp
+ It also requires the qhelpgenerator to be available on the system.
+ """
buildDir = os.path.join("docs", "build", "qthelp")
helpDir = os.path.join("nw", "assets", "help")
@@ -41,14 +36,14 @@ if buildDocs:
try:
subprocess.call(["make", "-C", "docs", "qthelp"])
except Exception as e:
- print("Failed with error:")
+ print("QtHelp Build Error:")
print(str(e))
buildFail = True
try:
subprocess.call(["qhelpgenerator", os.path.join(buildDir, inFile)])
except Exception as e:
- print("Failed with error:")
+ print("QtHelp Build Error:")
print(str(e))
buildFail = True
@@ -56,7 +51,7 @@ if buildDocs:
try:
os.mkdir(helpDir)
except Exception as e:
- print("Failed with error:")
+ print("QtHelp Build Error:")
print(str(e))
buildFail = True
@@ -68,7 +63,7 @@ if buildDocs:
os.rename(os.path.join(buildDir, outFile), os.path.join(helpDir, outFile))
os.rename(os.path.join(buildDir, datFile), os.path.join(helpDir, datFile))
except Exception as e:
- print("Failed with error:")
+ print("QtHelp Build Error:")
print(str(e))
buildFail = True
@@ -80,11 +75,20 @@ if buildDocs:
print("Documentation build: OK")
print("")
-##
-# Sample Project ZIP file
-##
+ return
-if buildSample:
+# =========================================================================== #
+# Sample Project ZIP File Builder
+# =========================================================================== #
+
+def buildSampleZip():
+ """Bundle the sample project into a single zip file to be saved into
+ the nw/assets folder for further bundling into builds.
+ """
+ print("")
+ print("Building Sample ZIP File")
+ print("========================")
+ print("")
srcSample = "sample"
dstSample = os.path.join("nw", "assets", "sample.zip")
@@ -96,8 +100,10 @@ if buildSample:
from zipfile import ZipFile
with ZipFile(dstSample, "w") as zipObj:
+ print("Compressing: nwProject.nwx")
zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx")
for docFile in os.listdir(os.path.join(srcSample, "content")):
+ print("Compressing: content/%s" % docFile)
srcDoc = os.path.join(srcSample, "content", docFile)
zipObj.write(srcDoc, "content/"+docFile)
@@ -105,12 +111,33 @@ if buildSample:
print("Error: Could not find sample project source directory.")
sys.exit(1)
-if len(sys.argv) == 1:
- # Nothing more to do
- sys.exit(0)
+ print("")
+ print("Built file: %s" % dstSample)
+ print("")
-##
-# Build the Package
-##
+ return
-setuptools.setup()
+# =========================================================================== #
+# Process Jobs
+# =========================================================================== #
+
+if __name__ == "__main__":
+
+ # Process non-standard jobs
+
+ if "qthelp" in sys.argv:
+ sys.argv.remove("qthelp")
+ buildQtDocs()
+
+ if "sample" in sys.argv:
+ sys.argv.remove("sample")
+ buildSampleZip()
+
+ if len(sys.argv) == 1:
+ # Nothing more to do
+ sys.exit(0)
+
+ # Run the standard setup
+ setuptools.setup()
+
+# END Main
From f0adc59d4cfcbe686f1c845ad2b2ad2463be5813 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 13:56:27 +0200
Subject: [PATCH 18/28] Rewritten make_windows.py script
---
make_windows.py | 378 +++++++++++++++++++++++++++++++-----------------
1 file changed, 247 insertions(+), 131 deletions(-)
diff --git a/make_windows.py b/make_windows.py
index 674fb023..766846b3 100755
--- a/make_windows.py
+++ b/make_windows.py
@@ -16,72 +16,22 @@ novelWriter itself.
import os
import sys
-import getopt
+import shutil
import subprocess
-if not sys.platform.startswith("win32"):
- print("ERROR: This script is intended for Windows only.")
- sys.exit(1)
+OS_NONE = 0
+OS_LINUX = 1
+OS_WIN = 2
+OS_DARWIN = 3
-# Defaults
-buildWindowed = True
-runPip = False
-oneFile = False
-makeSetup = False
-innoSetup = None
+# =============================================================================================== #
+# Package Installer
+# =============================================================================================== #
-# Parse Options
-shortOpt = "hd"
-longOpt = [
- "help",
- "debug",
- "pip",
- "onefile",
- "setup",
- "inno=",
-]
-helpMsg = (
- "\n"
- "novelWriter Install Script for Windows\n"
- "\n"
- "Usage:\n"
- " -h, --help Print this message.\n"
- " --pip Install dependecies first.\n"
- " --onefile Create a single executable file.\n"
- " --setup Make Inno Setup file.\n"
- " --inno= Path to the Inno Setup exec.\n"
- " -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n"
- " run it from command line with the debug options. Please check the\n"
- " novelWriter --help output for details.\n"
-)
-
-try:
- inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt)
-except getopt.GetoptError:
- print(helpMsg)
- sys.exit(1)
-
-for inOpt, inArg in inOpts:
- if inOpt in ("-h", "--help"):
- print(helpMsg)
- sys.exit(0)
- elif inOpt in ("-d", "--debug"):
- buildWindowed = False
- elif inOpt == "--pip":
- runPip = True
- elif inOpt == "--onefile":
- oneFile = True
- elif inOpt == "--setup":
- makeSetup = True
- elif inOpt == "--inno":
- innoSetup = inArg
-
-# Run pip
-if runPip:
+def installPackages():
print("")
- print("###########################")
- print(" Installing Dependencies")
- print("###########################")
+ print("Installing Dependencies")
+ print("#######################")
print("")
try:
subprocess.call([
@@ -101,86 +51,131 @@ if runPip:
print(str(e))
sys.exit(1)
-# Run pyinstaller
-print("")
-print("#######################")
-print(" Running PyInstaller")
-print("#######################")
-print("")
-instOpt = [
- "--name=novelWriter",
- "--clean",
- "--add-data=%s;%s" % (os.path.join("nw", "assets"), "assets"),
- "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
- "--exclude-module=PyQt5.QtQml",
- "--exclude-module=PyQt5.QtBluetooth",
- "--exclude-module=PyQt5.QtDBus",
- "--exclude-module=PyQt5.QtMultimedia",
- "--exclude-module=PyQt5.QtMultimediaWidgets",
- "--exclude-module=PyQt5.QtNetwork",
- "--exclude-module=PyQt5.QtNetworkAuth",
- "--exclude-module=PyQt5.QtNfc",
- "--exclude-module=PyQt5.QtQuick",
- "--exclude-module=PyQt5.QtQuickWidgets",
- "--exclude-module=PyQt5.QtRemoteObjects",
- "--exclude-module=PyQt5.QtSensors",
- "--exclude-module=PyQt5.QtSerialPort",
- "--exclude-module=PyQt5.QtSql",
-]
+ return
-if buildWindowed:
- instOpt.append("--windowed")
+# =============================================================================================== #
+# Run PyInstaller on Package
+# =============================================================================================== #
-if oneFile and not makeSetup:
- instOpt.append("--onefile")
-else:
- instOpt.append("--onedir")
+def freezePackage(buildWindowed, oneFile, makeSetup, hostOS):
+ """Run PyInstaller to freeze the packages. This assumes all
+ dependencies are already in place.
+ """
+ import PyInstaller.__main__ # noqa: E402
-instOpt.append("novelWriter.py")
+ print("")
+ print("Running PyInstaller")
+ print("###################")
+ print("")
-# Make sample.zip first
-print("Building sample.zip")
-try:
- subprocess.call([sys.executable, "setup.py", "sample"])
-except Exception as e:
- print("Failed with error:")
- print(str(e))
- sys.exit(1)
+ if hostOS == OS_WIN:
+ dotDot = ";"
+ else:
+ dotDot = ":"
-import PyInstaller.__main__ # noqa: E402
-PyInstaller.__main__.run(instOpt)
-
-if not oneFile:
- # These dll files are not nee3ded, and take up a fair bit of space.
- delIfExists = [
- "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll",
- "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll",
- "Qt5Quick3DRuntimeRender.dll", "Qt5Quick3DUtils.dll", "Qt5Sql.dll"
+ instOpt = [
+ "--name=novelWriter",
+ "--clean",
+ "--add-data=%s%s%s" % (os.path.join("nw", "assets"), dotDot, "assets"),
+ "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
+ "--exclude-module=PyQt5.QtQml",
+ "--exclude-module=PyQt5.QtBluetooth",
+ "--exclude-module=PyQt5.QtDBus",
+ "--exclude-module=PyQt5.QtMultimedia",
+ "--exclude-module=PyQt5.QtMultimediaWidgets",
+ "--exclude-module=PyQt5.QtNetwork",
+ "--exclude-module=PyQt5.QtNetworkAuth",
+ "--exclude-module=PyQt5.QtNfc",
+ "--exclude-module=PyQt5.QtQuick",
+ "--exclude-module=PyQt5.QtQuickWidgets",
+ "--exclude-module=PyQt5.QtRemoteObjects",
+ "--exclude-module=PyQt5.QtSensors",
+ "--exclude-module=PyQt5.QtSerialPort",
+ "--exclude-module=PyQt5.QtSql",
]
- distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
- for delFile in delIfExists:
- delPath = os.path.join(distDir, delFile)
- if os.path.isfile(delPath):
- print("Deleting file: %s" % delPath)
- os.unlink(delPath)
-print("")
-print("Build Finished")
-print("")
-print("If everything went well, the novelWriter executable should be in the folder named 'dist'")
-print("")
+ if buildWindowed:
+ instOpt.append("--windowed")
+
+ if oneFile and not makeSetup:
+ instOpt.append("--onefile")
+ else:
+ instOpt.append("--onedir")
+
+ instOpt.append("novelWriter.py")
+
+ # Make sample.zip first
+ try:
+ subprocess.call([sys.executable, "setup.py", "sample"])
+ except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
+
+ PyInstaller.__main__.run(instOpt)
+
+ if not oneFile:
+ # These files are not needed, and take up a fair bit of space.
+ delFiles = []
+ if hostOS == OS_WIN:
+ delFiles = [
+ "Qt5DBus.dll",
+ "Qt5Network.dll",
+ "Qt5Qml.dll",
+ "Qt5QmlModels.dll",
+ "Qt5Quick.dll",
+ "Qt5Quick3D.dll",
+ "Qt5Quick3DAssetImport.dll",
+ "Qt5Quick3DRender.dll",
+ "Qt5Quick3DRuntimeRender.dll",
+ "Qt5Quick3DUtils.dll",
+ "Qt5Sql.dll"
+ ]
+ elif hostOS == OS_LINUX:
+ delFiles = [
+ "libQt5DBus.so.5",
+ "libQt5Network.so.5",
+ "libQt5Qml.so.5",
+ "libQt5QmlModels.so.5",
+ "libQt5Quick.so.5",
+ "libQt5Quick3D.so.5",
+ "libQt5Quick3DAssetImport.so.5",
+ "libQt5Quick3DRender.so.5",
+ "libQt5Quick3DRuntimeRender.so.5",
+ "libQt5Quick3DUtils.so.5",
+ "libQt5Sql.so.5"
+ ]
+ distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
+ for delFile in delFiles:
+ delPath = os.path.join(distDir, delFile)
+ if os.path.isfile(delPath):
+ print("Deleting file: %s" % delPath)
+ os.unlink(delPath)
-if makeSetup:
print("")
- print("######################")
- print(" Running Inno Setup")
- print("######################")
+ print("Build Finished")
print("")
- if innoSetup is None:
- innoSetup = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
- if not os.path.isfile(innoSetup):
+ print("The novelWriter executable should be in the folder named 'dist'")
+ print("")
+
+ return
+
+# =============================================================================================== #
+# Inno Setup Builder
+# =============================================================================================== #
+
+def innoSetup(innoExec):
+ """Run the Inno Setup tool to build a setup.exe file for Windows.
+ """
+ print("")
+ print("Running Inno Setup")
+ print("##################")
+ print("")
+ if innoExec is None:
+ innoExec = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
+ if not os.path.isfile(innoExec):
print("ERROR: Cannot fine Inno Setup's ISCC.exe file.")
- print(" Looked in: %s" % innoSetup)
+ print(" Looked in: %s" % innoExec)
print(" Please provide a path with the --inno= option.")
sys.exit(1)
@@ -197,8 +192,129 @@ if makeSetup:
outFile.write(issData)
try:
- subprocess.call([innoSetup, "setup.iss"])
+ subprocess.call([innoExec, "setup.iss"])
except Exception as e:
print("Failed with error:")
print(str(e))
sys.exit(1)
+
+ return
+
+# =============================================================================================== #
+# Clean Build and Dist Folders
+# =============================================================================================== #
+
+def cleanInstall():
+ """Recursively delete the 'build' and 'dist' folders.
+ """
+ print("")
+ print("Cleaning up build environment ...")
+
+ buildDir = os.path.join(os.getcwd(), "build")
+ if os.path.isdir(buildDir):
+ try:
+ shutil.rmtree(buildDir)
+ print("Deleted folder 'build'")
+ except Exception as e:
+ print("Error: Cannot delete 'build' folder.")
+ print(str(e))
+ sys.exit(1)
+ else:
+ print("Folder 'build' not found")
+
+ distDir = os.path.join(os.getcwd(), "dist")
+ if os.path.isdir(distDir):
+ try:
+ shutil.rmtree(distDir)
+ print("Deleted folder 'dist'")
+ except Exception as e:
+ print("Error: Cannot delete 'dist' folder.")
+ print(str(e))
+ sys.exit(1)
+ else:
+ print("Folder 'dist' not found")
+
+ print("")
+
+ return
+
+# =============================================================================================== #
+# Process Build Steps
+# =============================================================================================== #
+
+if __name__ == "__main__":
+ """Parse command line options and run the commands.
+ """
+ # Detect OS
+ if sys.platform.startswith("linux"):
+ hostOS = OS_LINUX
+ elif sys.platform.startswith("darwin"):
+ hostOS = OS_DARWIN
+ elif sys.platform.startswith("win32"):
+ hostOS = OS_WIN
+ elif sys.platform.startswith("cygwin"):
+ hostOS = OS_WIN
+ else:
+ hostOS = OS_NONE
+
+ # Flags and Variables
+ buildWindowed = True
+ oneFile = False
+ makeSetup = False
+ innoExec = None
+
+ if "help" in sys.argv:
+ print(
+ "\n"
+ "novelWriter Make Tool\n"
+ "=====================\n"
+ "This tool provides build commands for distibuting novelWriter as\n"
+ "a package. The available options are as follows:\n"
+ "\n"
+ "pip Run pip to install all package dependencies for\n"
+ " novelWriter and this build tool.\n"
+ "onefile Build a standalone executable with all dependencies\n"
+ " bundled. This does not produce a setup.exe on Windows.\n"
+ "setup Build a setup.exe installer for Windows. This option\n"
+ " automaticall disables the 'onefile' option.\n"
+ "clean This will attempt to delete the 'build' and 'dist'\n"
+ " folders in the current folder.\n"
+ )
+ sys.exit(0)
+
+ if not os.path.isfile(os.path.join(os.getcwd(), "novelWriter.py")):
+ print("Error: This script must be run in the root folder of novelWriter.")
+ sys.exit(1)
+
+ if not os.path.isdir(os.path.join(os.getcwd(), "nw")):
+ print("Error: This script must be run in the root folder of novelWriter.")
+ sys.exit(1)
+
+ if "clean" in sys.argv:
+ sys.argv.remove("clean")
+ cleanInstall()
+ sys.exit(0)
+
+ if "pip" in sys.argv:
+ sys.argv.remove("pip")
+ installPackages()
+
+ if "onefile" in sys.argv:
+ sys.argv.remove("onefile")
+ oneFile = True
+
+ if "setup" in sys.argv:
+ sys.argv.remove("setup")
+ if hostOS == OS_WIN:
+ oneFile = False
+ makeSetup = True
+ else:
+ print("Error: Argument 'setup' for Inno Setup is Windows only.")
+ sys.exit(1)
+
+ freezePackage(buildWindowed, oneFile, makeSetup, hostOS)
+
+ if makeSetup:
+ innoSetup(innoExec)
+
+# END Main
From a20296cded774a572150f37ea9ab4bd013d7a5e7 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 13:57:19 +0200
Subject: [PATCH 19/28] Rnamed make_windows.py to make.py, and deleted
install.py
---
install.py | 84 --------------------------------------
make_windows.py => make.py | 0
2 files changed, 84 deletions(-)
delete mode 100755 install.py
rename make_windows.py => make.py (100%)
diff --git a/install.py b/install.py
deleted file mode 100755
index 11754a26..00000000
--- a/install.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import os
-import sys
-import getopt
-import subprocess
-
-# Defaults
-buildWindowed = True
-
-# Parse Options
-shortOpt = "hd"
-longOpt = [
- "help",
- "debug",
-]
-helpMsg = (
- "\n"
- "novelWriter Install Script\n"
- "\n"
- "Usage:\n"
- " -h, --help Print this message.\n"
- " -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n"
- " run it from command line with the debug options. Please check the\n"
- " novelWriter --help output for details.\n"
-)
-
-try:
- inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt)
-except getopt.GetoptError:
- print(helpMsg)
- sys.exit(2)
-
-for inOpt, inArg in inOpts:
- if inOpt in ("-h", "--help"):
- print(helpMsg)
- sys.exit(0)
- elif inOpt in ("-d", "--debug"):
- buildWindowed = False
-
-# Run pip
-packList = ["pyinstaller"]
-with open("requirements.txt", mode="r") as reqFile:
- for reqPack in reqFile:
- if len(reqPack.strip()) > 0:
- packList.append(reqPack)
-
-for packName in packList:
- print("Installing package dependency: %s" % packName)
- try:
- subprocess.call([sys.executable, "-m", "pip", "install", packName])
- except Exception as e:
- print("Failed with error:")
- print(str(e))
-
-# Run pyinstaller
-if sys.platform.startswith("win32"):
- dotDot = ";"
-else:
- dotDot = ":"
-
-instOpt = [
- "--name=novelWriter",
- "--clean",
- "--onefile",
- "--add-data=%s%s%s" % (os.path.join("nw", "assets"), dotDot, "assets"),
- "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
-]
-if buildWindowed:
- instOpt.append("--windowed")
-
-instOpt.append("novelWriter.py")
-
-import PyInstaller.__main__ # noqa: E402
-PyInstaller.__main__.run(instOpt)
-
-print("")
-print("##################")
-print(" Build Finished")
-print("##################")
-print("")
-print("If everything went well, the novelWriter executable should be in the folder named 'dist'")
-print("")
diff --git a/make_windows.py b/make.py
similarity index 100%
rename from make_windows.py
rename to make.py
From db2eb98c274e36a545e962f35c199cd1aa69df1f Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 14:09:32 +0200
Subject: [PATCH 20/28] Minor changes to the windows inno setup
---
make.py | 20 ++++++--------------
setup/win_setup.iss | 2 +-
2 files changed, 7 insertions(+), 15 deletions(-)
diff --git a/make.py b/make.py
index 766846b3..6162a313 100755
--- a/make.py
+++ b/make.py
@@ -164,20 +164,13 @@ def freezePackage(buildWindowed, oneFile, makeSetup, hostOS):
# Inno Setup Builder
# =============================================================================================== #
-def innoSetup(innoExec):
+def innoSetup():
"""Run the Inno Setup tool to build a setup.exe file for Windows.
"""
print("")
print("Running Inno Setup")
print("##################")
print("")
- if innoExec is None:
- innoExec = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
- if not os.path.isfile(innoExec):
- print("ERROR: Cannot fine Inno Setup's ISCC.exe file.")
- print(" Looked in: %s" % innoExec)
- print(" Please provide a path with the --inno= option.")
- sys.exit(1)
# Read the iss template
issData = ""
@@ -192,9 +185,9 @@ def innoSetup(innoExec):
outFile.write(issData)
try:
- subprocess.call([innoExec, "setup.iss"])
+ subprocess.call(["iscc", "setup.iss"])
except Exception as e:
- print("Failed with error:")
+ print("Inno Setup failed with error:")
print(str(e))
sys.exit(1)
@@ -261,7 +254,6 @@ if __name__ == "__main__":
buildWindowed = True
oneFile = False
makeSetup = False
- innoExec = None
if "help" in sys.argv:
print(
@@ -271,10 +263,10 @@ if __name__ == "__main__":
"This tool provides build commands for distibuting novelWriter as\n"
"a package. The available options are as follows:\n"
"\n"
- "pip Run pip to install all package dependencies for\n"
- " novelWriter and this build tool.\n"
"onefile Build a standalone executable with all dependencies\n"
" bundled. This does not produce a setup.exe on Windows.\n"
+ "pip Run pip to install all package dependencies for\n"
+ " novelWriter and this build tool.\n"
"setup Build a setup.exe installer for Windows. This option\n"
" automaticall disables the 'onefile' option.\n"
"clean This will attempt to delete the 'build' and 'dist'\n"
@@ -315,6 +307,6 @@ if __name__ == "__main__":
freezePackage(buildWindowed, oneFile, makeSetup, hostOS)
if makeSetup:
- innoSetup(innoExec)
+ innoSetup()
# END Main
diff --git a/setup/win_setup.iss b/setup/win_setup.iss
index 6491b317..83f016b6 100644
--- a/setup/win_setup.iss
+++ b/setup/win_setup.iss
@@ -27,7 +27,7 @@ UsedUserAreasWarning=no
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir={#nwAppDir}
-OutputBaseFilename=setup-novelwriter-{#nwAppVersion}
+OutputBaseFilename=novelwriter_{#nwAppVersion}_win10_full_setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
From 614572ca80e70a6602979c646465b91b64b7a2ea Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 15:05:58 +0200
Subject: [PATCH 21/28] Minor modifications to make.py, and made Inno Setyp 64
bit
---
make.py | 47 ++++++++++++++++++++++++++++-----------------
setup/win_setup.iss | 3 ++-
2 files changed, 31 insertions(+), 19 deletions(-)
diff --git a/make.py b/make.py
index 6162a313..7a0923b9 100755
--- a/make.py
+++ b/make.py
@@ -254,24 +254,29 @@ if __name__ == "__main__":
buildWindowed = True
oneFile = False
makeSetup = False
+ doFreeze = False
- if "help" in sys.argv:
- print(
- "\n"
- "novelWriter Make Tool\n"
- "=====================\n"
- "This tool provides build commands for distibuting novelWriter as\n"
- "a package. The available options are as follows:\n"
- "\n"
- "onefile Build a standalone executable with all dependencies\n"
- " bundled. This does not produce a setup.exe on Windows.\n"
- "pip Run pip to install all package dependencies for\n"
- " novelWriter and this build tool.\n"
- "setup Build a setup.exe installer for Windows. This option\n"
- " automaticall disables the 'onefile' option.\n"
- "clean This will attempt to delete the 'build' and 'dist'\n"
- " folders in the current folder.\n"
- )
+ helpMsg = (
+ "\n"
+ "novelWriter Make Tool\n"
+ "=====================\n"
+ "This tool provides build commands for distibuting novelWriter as a\n"
+ "package. The available options are as follows:\n"
+ "\n"
+ "freeze Freeze the package and produces a folder of all\n"
+ " dependecies using pyinstaller.\n"
+ "onefile Build a standalone executable with all dependencies\n"
+ " bundled. Implies 'freeze', cannot be used with 'setup'.\n"
+ "pip Run pip to install all package dependencies for\n"
+ " novelWriter and this build tool.\n"
+ "setup Build a setup.exe installer for Windows. This option\n"
+ " automaticall disables the 'onefile' option.\n"
+ "clean This will attempt to delete the 'build' and 'dist'\n"
+ " folders in the current folder.\n"
+ )
+
+ if "help" in sys.argv or len(sys.argv) <= 1:
+ print(helpMsg)
sys.exit(0)
if not os.path.isfile(os.path.join(os.getcwd(), "novelWriter.py")):
@@ -291,8 +296,13 @@ if __name__ == "__main__":
sys.argv.remove("pip")
installPackages()
+ if "freeze" in sys.argv:
+ sys.argv.remove("freeze")
+ doFreeze = True
+
if "onefile" in sys.argv:
sys.argv.remove("onefile")
+ doFreeze = True
oneFile = True
if "setup" in sys.argv:
@@ -304,7 +314,8 @@ if __name__ == "__main__":
print("Error: Argument 'setup' for Inno Setup is Windows only.")
sys.exit(1)
- freezePackage(buildWindowed, oneFile, makeSetup, hostOS)
+ if doFreeze:
+ freezePackage(buildWindowed, oneFile, makeSetup, hostOS)
if makeSetup:
innoSetup()
diff --git a/setup/win_setup.iss b/setup/win_setup.iss
index 83f016b6..8b6be609 100644
--- a/setup/win_setup.iss
+++ b/setup/win_setup.iss
@@ -27,10 +27,11 @@ UsedUserAreasWarning=no
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir={#nwAppDir}
-OutputBaseFilename=novelwriter_{#nwAppVersion}_win10_full_setup
+OutputBaseFilename=novelwriter_{#nwAppVersion}_win_amd64_setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
+ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
From 3378b387cfdc5d7f2feec98ae881c86abdb2f004 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 15:52:40 +0200
Subject: [PATCH 22/28] Reduce size of freeze build and added more
documentation
---
README.md | 2 ++
make.py | 50 +++++++++++++++++++++++++++++---------------------
setup/BUILD.md | 44 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 75 insertions(+), 21 deletions(-)
create mode 100644 setup/BUILD.md
diff --git a/README.md b/README.md
index 3b7835ed..be86e0f4 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,8 @@ for debugging.
You can also provide a path to a folder containing a novelWriter project as the last parameter.
+For more install options, see [Build and Install novelWriter](setup/BUILD.md).
+
### Launcher and Icons
diff --git a/make.py b/make.py
index 7a0923b9..15ed18ae 100755
--- a/make.py
+++ b/make.py
@@ -28,28 +28,29 @@ OS_DARWIN = 3
# Package Installer
# =============================================================================================== #
-def installPackages():
+def installPackages(hostOS):
+ """Install package dependencies both for this script and for running
+ novelWriter itself.
+ """
print("")
print("Installing Dependencies")
print("#######################")
print("")
- try:
- subprocess.call([
- sys.executable, "-m",
- "pip", "install", "--user", "--upgrade", "pip"
- ])
- subprocess.call([
- sys.executable, "-m",
- "pip", "install", "--user", "--upgrade", "pyinstaller"
- ])
- subprocess.call([
- sys.executable, "-m",
- "pip", "install", "--user", "--upgrade", "-r", "requirements.txt"
- ])
- except Exception as e:
- print("Failed with error:")
- print(str(e))
- sys.exit(1)
+
+ installQueue = ["pip", "pyinstaller", "-r requirements.txt"]
+ if hostOS == OS_DARWIN:
+ installQueue.append("pyobjc")
+
+ pyCmd = [sys.executable, "-m"]
+ pipCmd = ["pip", "install", "--user", "--upgrade"]
+ for stepCmd in installQueue:
+ pkgCmd = stepCmd.split(" ")
+ try:
+ subprocess.call(pyCmd + pipCmd + pkgCmd)
+ except Exception as e:
+ print("Failed with error:")
+ print(str(e))
+ sys.exit(1)
return
@@ -73,6 +74,7 @@ def freezePackage(buildWindowed, oneFile, makeSetup, hostOS):
else:
dotDot = ":"
+ sys.modules["FixTk"] = None
instOpt = [
"--name=novelWriter",
"--clean",
@@ -92,6 +94,12 @@ def freezePackage(buildWindowed, oneFile, makeSetup, hostOS):
"--exclude-module=PyQt5.QtSensors",
"--exclude-module=PyQt5.QtSerialPort",
"--exclude-module=PyQt5.QtSql",
+ "--exclude-module=FixTk",
+ "--exclude-module=tcl",
+ "--exclude-module=tk",
+ "--exclude-module=_tkinter",
+ "--exclude-module=tkinter",
+ "--exclude-module=Tkinter",
]
if buildWindowed:
@@ -263,8 +271,9 @@ if __name__ == "__main__":
"This tool provides build commands for distibuting novelWriter as a\n"
"package. The available options are as follows:\n"
"\n"
+ "help Print the help message.\n"
"freeze Freeze the package and produces a folder of all\n"
- " dependecies using pyinstaller.\n"
+ " dependencies using pyinstaller.\n"
"onefile Build a standalone executable with all dependencies\n"
" bundled. Implies 'freeze', cannot be used with 'setup'.\n"
"pip Run pip to install all package dependencies for\n"
@@ -290,11 +299,10 @@ if __name__ == "__main__":
if "clean" in sys.argv:
sys.argv.remove("clean")
cleanInstall()
- sys.exit(0)
if "pip" in sys.argv:
sys.argv.remove("pip")
- installPackages()
+ installPackages(hostOS)
if "freeze" in sys.argv:
sys.argv.remove("freeze")
diff --git a/setup/BUILD.md b/setup/BUILD.md
new file mode 100644
index 00000000..d97e702a
--- /dev/null
+++ b/setup/BUILD.md
@@ -0,0 +1,44 @@
+# Build and Install novelWriter
+
+The root folder of the repository contains two scripts for setup and install:
+
+## Script `setup.py`
+
+The `setup.py` is a standard Python setup script with a couple of additional options:
+
+* `qthelp`: Will attempt to build a single file QtAssistand documentation file.
+ This requires the Qt tools to be installed on the local system, as well as the sphinx build tools
+ for the documentation.
+* `sample`: Will create a `sample.zip` file in the `nw/assets` folder.
+ This is the file the New Project Wizard uses to generate an example project.
+ If novelWriter is run from source, this file is not needed.
+
+To install novelWriter as a local Python package, run:
+```bash
+sudo python setup.py install
+```
+
+## Script `make.py`
+
+The `make.py` script provides a number of convenient options for building packages if novelWriter.
+
+Usage:
+```bash
+python make.py [command]
+```
+
+It currently accept the following commands:
+
+* `help`: Print the help message.
+* `freeze`: Freeze the package and produces a folder of all dependencies using pyinstaller.
+* `onefile`: Build a standalone executable with all dependencies bundled.
+ Implies `freeze`, cannot be used with `setup`.
+* `pip`: Run pip to install all package dependencies for novelWriter and this build tool.
+* `setup`: Build a setup.exe installer for Windows.
+ This option automaticall disables the `onefile` option.
+* `clean`: This will attempt to delete the `build` and `dist` folders in the current folder.
+
+For instance, to create a Windows installer, run:
+```bash
+python make.py freeze setup
+```
From 139293b21cfb340c9a70cca279e80021fc35765f Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 16:58:47 +0200
Subject: [PATCH 23/28] Added a launcher option to setup.py, and updated
documentation
---
README.md | 47 +++++----
setup.py | 181 +++++++++++++++++++++++++++++++++--
setup/BUILD.md | 7 +-
setup/installDebianUbuntu.sh | 42 --------
4 files changed, 209 insertions(+), 68 deletions(-)
delete mode 100755 setup/installDebianUbuntu.sh
diff --git a/README.md b/README.md
index be86e0f4..f8d19eb6 100644
--- a/README.md
+++ b/README.md
@@ -65,30 +65,42 @@ You can update novelWriter to the latest version by running:
pip install --upgrade novelwriter
```
-The application can then be started with one of the commands, depending on your Python configuration:
-```bash
-./novelWriter.py
-python novelWriter.py
-python3 novelWriter.py
-```
-
It also takes a few parameters for debugging and such, which can be listed with the switch `--help`.
The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output
for debugging.
You can also provide a path to a folder containing a novelWriter project as the last parameter.
+## Installing from Source (Linux)
+
+You can then install novelWriter from the source directly with:
+```bash
+python3 setup.py sample
+sudo python3 setup.py install
+sudo python3 setup.py launcher
+```
+
+The last line will install the application icons and set up a launcher for novelWriter.
+The method uses hardcoded paths, so it may or may not work for your Linux distro.
+
+It may prompt you to choose which executable to configure.
+You can also use this to configure it to run from source.
+
+## Running from Source (Linux)
+
+If you want to run directly from the source, the application can be started with:
+```bash
+./novelWriter.py
+```
+
+You can also create a launcher for the source with:
+```bash
+sudo python3 setup.py launcher
+```
+
For more install options, see [Build and Install novelWriter](setup/BUILD.md).
-### Launcher and Icons
-
-In the root setup folder there are icons and scripts and a template for setting up a launcher on
-Gnome desktops. You may need to modify those scripts slightly, but as they are, they work on Debian
-and Ubuntu. For other operating systems, please consult your operating system documentation for how
-to make those. Feel free to submit more if you are able to make them.
-
-
## Package Dependencies
It is recommended that novelWriter runs with Qt 5.10 or later, and requires Python 3.6 or later.
@@ -99,7 +111,7 @@ Minimum version of Qt is 5.2.
Generally, dependencies can be installed via `pip` with:
```bash
-pip3 install -r requirements.txt
+pip install -r requirements.txt
```
You can also install the packages from the distro's own package manager.
@@ -144,6 +156,9 @@ It should look something like this:
C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py
```
+You can also run the `make.py` script to generate an installer.
+See [Build and Install novelWriter](setup/BUILD.md) for more details.
+
### Package Versions
diff --git a/setup.py b/setup.py
index 403a71f2..4683e3f2 100755
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
import os
import sys
+import shutil
import subprocess
import setuptools
-# =========================================================================== #
+# =============================================================================================== #
# Qt Assistant Documentation Builder
-# =========================================================================== #
+# =============================================================================================== #
def buildQtDocs():
"""This function will build the documentation as a Qt help file. The
@@ -77,9 +78,9 @@ def buildQtDocs():
return
-# =========================================================================== #
+# =============================================================================================== #
# Sample Project ZIP File Builder
-# =========================================================================== #
+# =============================================================================================== #
def buildSampleZip():
"""Bundle the sample project into a single zip file to be saved into
@@ -117,13 +118,173 @@ def buildSampleZip():
return
-# =========================================================================== #
+# =============================================================================================== #
+# Create Launcher
+# =============================================================================================== #
+
+def makeLauncherLinux():
+ """Will attempt to install icons and make a launcher.
+ """
+ print("")
+ print("Creating Launcher")
+ print("=================")
+ print("")
+
+ exOpts = []
+
+ testExec = shutil.which("novelWriter")
+ if testExec is not None:
+ exOpts.append(testExec)
+
+ testExec = shutil.which("novelwriter")
+ if testExec is not None:
+ exOpts.append(testExec)
+
+ testExec = os.path.join(os.getcwd(), "novelWriter.py")
+ if os.path.isfile(testExec):
+ exOpts.append(testExec)
+
+ useExec = ""
+ nOpts = len(exOpts)
+ if nOpts == 0:
+ print("Error: No executables for novelWriter found.")
+ sys.exit(1)
+ elif nOpts == 1:
+ useExec = exOpts[0]
+ else:
+ print("Found multiple novelWriter executables:")
+ print("")
+ for iExec, anExec in enumerate(exOpts):
+ print(" [%d] %s" % (iExec, anExec))
+ print("")
+ intVal = int(input("Please select which novelWriter executable to use: "))
+ print("")
+
+ if intVal >= 0 and intVal < nOpts:
+ useExec = exOpts[intVal]
+ else:
+ print("Error: Invalid selection.")
+ sys.exit(1)
+
+ print("Using executable: %s " % useExec)
+
+ # Read the Template
+ desktopData = ""
+ with open(os.path.join("setup", "novelwriter.desktop"), mode="r") as inFile:
+ desktopData = inFile.read()
+
+ desktopData = desktopData.replace(r"%%exec%%", useExec)
+
+ desktopFile = "/usr/share/applications/novelwriter.desktop"
+ try:
+ with open(desktopFile, mode="w+") as outFile:
+ outFile.write(desktopData)
+ print("Wrote file: %s" % desktopFile)
+ except Exception as e:
+ print("Error: Could not write novelwriter.desktop file.")
+ print(str(e))
+ sys.exit(1)
+
+ print("")
+
+ # Copy Icons
+
+ iconDirs = [
+ "/usr/share/icons/hicolor/24x24/apps",
+ "/usr/share/icons/hicolor/48x48/apps",
+ "/usr/share/icons/hicolor/96x96/apps",
+ "/usr/share/icons/hicolor/256x256/apps",
+ "/usr/share/icons/hicolor/512x512/apps",
+ "/usr/share/icons/hicolor/scalable/apps",
+ "/usr/share/icons/hicolor/scalable/mimetypes",
+ ]
+ for iconDir in iconDirs:
+ if not os.path.isdir:
+ try:
+ os.mkdir(iconDir)
+ print("Created folder: %s" % iconDir)
+ except Exception as e:
+ print("Error: Could not make folder: %s" % iconDir)
+ print(str(e))
+
+ copyList = [(
+ "setup/icons/24x24/novelwriter.png",
+ "/usr/share/icons/hicolor/24x24/apps/novelwriter.png"
+ ), (
+ "setup/icons/48x48/novelwriter.png",
+ "/usr/share/icons/hicolor/48x48/apps/novelwriter.png"
+ ), (
+ "setup/icons/96x96/novelwriter.png",
+ "/usr/share/icons/hicolor/96x96/apps/novelwriter.png"
+ ), (
+ "setup/icons/256x256/novelwriter.png",
+ "/usr/share/icons/hicolor/256x256/apps/novelwriter.png"
+ ), (
+ "setup/icons/512x512/novelwriter.png",
+ "/usr/share/icons/hicolor/512x512/apps/novelwriter.png"
+ ), (
+ "setup/icons/novelwriter.svg",
+ "/usr/share/icons/hicolor/scalable/apps/novelwriter.svg"
+ ), (
+ "setup/icons/x-novelwriter-project.svg",
+ "/usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg"
+ ), (
+ "setup/mime/x-novelwriter-project.xml",
+ "/usr/share/mime/packages/x-novelwriter-project.xml"
+ )]
+ for srcFile, dstFile in copyList:
+ try:
+ shutil.copyfile(srcFile, dstFile)
+ print("Copied file to: %s" % dstFile)
+ except Exception as e:
+ print("Error: Could not copy file: %s" % srcFile)
+ print(str(e))
+
+ print("")
+
+ # Update System
+ try:
+ subprocess.call(["update-mime-database", "/usr/share/mime/"])
+ print("Updated mime database.")
+ except Exception as e:
+ print("Error: Filed to update mime database.")
+ print(str(e))
+
+ try:
+ subprocess.call(["update-icon-caches", "/usr/share/icons/*"])
+ print("Updated icon cache.")
+ except Exception as e:
+ print("Error: Filed to update icon cache.")
+ print(str(e))
+
+ print("")
+ print("Done!")
+ print("")
+
+ return
+
+# =============================================================================================== #
# Process Jobs
-# =========================================================================== #
+# =============================================================================================== #
if __name__ == "__main__":
- # Process non-standard jobs
+ helpMsg = (
+ "\n"
+ "novelWriter Setup Tool\n"
+ "======================\n"
+ "This tool provides some additional setup commands for novelWriter.\n"
+ "\n"
+ "help Print the help message.\n"
+ "gthelp Build the help documentation for use with the QtAssistant.\n"
+ "sample Build the sample project as a zip file.\n"
+ "launcher Install launcher icons for freedesktop systems.\n"
+ )
+
+ if "help" in sys.argv:
+ sys.argv.remove("help")
+ print(helpMsg)
+ sys.exit(0)
if "qthelp" in sys.argv:
sys.argv.remove("qthelp")
@@ -133,7 +294,11 @@ if __name__ == "__main__":
sys.argv.remove("sample")
buildSampleZip()
- if len(sys.argv) == 1:
+ if "launcher" in sys.argv:
+ sys.argv.remove("launcher")
+ makeLauncherLinux()
+
+ if len(sys.argv) <= 1:
# Nothing more to do
sys.exit(0)
diff --git a/setup/BUILD.md b/setup/BUILD.md
index d97e702a..81479cf7 100644
--- a/setup/BUILD.md
+++ b/setup/BUILD.md
@@ -2,6 +2,7 @@
The root folder of the repository contains two scripts for setup and install:
+
## Script `setup.py`
The `setup.py` is a standard Python setup script with a couple of additional options:
@@ -10,8 +11,10 @@ The `setup.py` is a standard Python setup script with a couple of additional opt
This requires the Qt tools to be installed on the local system, as well as the sphinx build tools
for the documentation.
* `sample`: Will create a `sample.zip` file in the `nw/assets` folder.
- This is the file the New Project Wizard uses to generate an example project.
- If novelWriter is run from source, this file is not needed.
+ This is the file the New Project Wizard uses to generate an example project.
+ If novelWriter is run from source, this file is not needed.
+* `launcher`: Will try to copy the novelWriter icons and create a novelWriter.desktop file to launch
+ the application. This should work on standard Linux desktops.
To install novelWriter as a local Python package, run:
```bash
diff --git a/setup/installDebianUbuntu.sh b/setup/installDebianUbuntu.sh
deleted file mode 100755
index c47f4d26..00000000
--- a/setup/installDebianUbuntu.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/bash
-
-cd ..
-
-EXEC=$(pwd)/novelWriter.py
-EXEC=$(echo $EXEC | sed 's_/_\\/_g')
-
-sed "s/%%exec%%/$EXEC/g" setup/novelwriter.desktop > /usr/share/applications/novelwriter.desktop
-
-if [ ! -d /usr/share/icons/hicolor/24x24/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/24x24/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/48x48/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/48x48/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/96x96/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/96x96/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/256x256/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/256x256/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/512x512/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/512x512/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/scalable/apps ]; then
- mkdir -pv /usr/share/icons/hicolor/scalable/apps
-fi
-if [ ! -d /usr/share/icons/hicolor/scalable/mimetypes ]; then
- mkdir -pv /usr/share/icons/hicolor/scalable/mimetypes
-fi
-
-cp -v setup/icons/24x24/novelwriter.png /usr/share/icons/hicolor/24x24/apps/
-cp -v setup/icons/48x48/novelwriter.png /usr/share/icons/hicolor/48x48/apps/
-cp -v setup/icons/96x96/novelwriter.png /usr/share/icons/hicolor/96x96/apps/
-cp -v setup/icons/256x256/novelwriter.png /usr/share/icons/hicolor/256x256/apps/
-cp -v setup/icons/512x512/novelwriter.png /usr/share/icons/hicolor/512x512/apps/
-cp -v setup/icons/novelwriter.svg /usr/share/icons/hicolor/scalable/apps/
-cp -v setup/icons/x-novelwriter-project.svg /usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg
-cp -v setup/mime/x-novelwriter-project.xml /usr/share/mime/packages/
-
-update-mime-database /usr/share/mime/
-update-icon-caches /usr/share/icons/*
From 2ffca7db48c8f115dac9de07d96c691528d7c0b1 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 17:26:04 +0200
Subject: [PATCH 24/28] Updated and cleaned up the readme
---
README.md | 84 +++++++++++++++++++++++++++----------------------------
1 file changed, 42 insertions(+), 42 deletions(-)
diff --git a/README.md b/README.md
index f8d19eb6..ce5cfdf0 100644
--- a/README.md
+++ b/README.md
@@ -52,28 +52,37 @@ in principle work fine on other operating systems as well as long as dependencie
tests are run on the latest versions of Ubuntu Linux, Windows Server and macOS.
-## Installing and Running
+# Installing and Running
-You can runt novelWriter either from a downloaded copy of the source code, or by running:
+novelWriter is available on [pypi.org](https://pypi.org/project/novelWriter/), and can be installed with:
```bash
pip install novelwriter
```
-**Note:** On some systems you must use `pip3` instead for the Python 3 version.
-You can update novelWriter to the latest version by running:
+To upgrade an existing installation, use:
```bash
pip install --upgrade novelwriter
```
-It also takes a few parameters for debugging and such, which can be listed with the switch `--help`.
-The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output
-for debugging.
+Dependencies are installed automatically, but can generally be installed with:
+```bash
+pip install -r requirements.txt
+```
-You can also provide a path to a folder containing a novelWriter project as the last parameter.
+Below are some brief instructions on how to get started on different operating systems.
-## Installing from Source (Linux)
-You can then install novelWriter from the source directly with:
+## Linux
+
+Either download the source, or install with pip.
+
+If you run from source, install the dependencies via pip, or directly from the OS repo.
+There are very few dependencies, and they should be available in the standard repo.
+The Python packages needed are `pyqt5`, `lxml` and `pyenchant`.
+
+### Installing from Source
+
+You can also install novelWriter from source with:
```bash
python3 setup.py sample
sudo python3 setup.py install
@@ -82,18 +91,18 @@ sudo python3 setup.py launcher
The last line will install the application icons and set up a launcher for novelWriter.
The method uses hardcoded paths, so it may or may not work for your Linux distro.
+If you have any issues, please submit a ticket so the script can be tuned.
-It may prompt you to choose which executable to configure.
-You can also use this to configure it to run from source.
+The script may prompt you to choose which executable to configure if it finds more than one.
-## Running from Source (Linux)
+### Running from Source
If you want to run directly from the source, the application can be started with:
```bash
./novelWriter.py
```
-You can also create a launcher for the source with:
+You can also create a launcher for running directly from source with:
```bash
sudo python3 setup.py launcher
```
@@ -101,28 +110,7 @@ sudo python3 setup.py launcher
For more install options, see [Build and Install novelWriter](setup/BUILD.md).
-## Package Dependencies
-
-It is recommended that novelWriter runs with Qt 5.10 or later, and requires Python 3.6 or later.
-Minimum version of Qt is 5.2.
-
-
-### Linux
-
-Generally, dependencies can be installed via `pip` with:
-```bash
-pip install -r requirements.txt
-```
-
-You can also install the packages from the distro's own package manager.
-For the apt package manager on Debian/Ubuntu systems, the following Python3 packages are needed:
-
-* `python3-pyqt5` for the GUI
-* `python3-lxml` for writing project files
-* `python3-enchant` for better spell checking (optional)
-
-
-### macOS
+## macOS
These instructions assume you're using brew, and have Python and pip set up.
If not, see the [brew docs](https://docs.brew.sh/Homebrew-and-Python) for help.
@@ -140,11 +128,16 @@ It comes with a lot of default dictionaries.
brew install enchant
```
-
### Windows
-On Windows, the `pip install` command is generally sufficient to install everything you need.
-That should also install the Qt libraries and the spell check dictionary dependencies.
+On Windows, you may first need to install Python.
+See the [python.org](https://www.python.org/) website for download packages.
+It is recommended that you install the latest version of Python 3.8.
+
+To install dependencies, run:
+```bash
+pip install --user -r requirements.txt
+```
**Note:** On Windows, make sure Python3 is in your PATH if you want to launch novelWriter from
command line. You can also right click the `novelWriter.py` file, create a shortcut, then right
@@ -156,11 +149,11 @@ It should look something like this:
C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py
```
-You can also run the `make.py` script to generate an installer.
+You can also run the `make.py` script to generate a single executable, or an installer.
See [Build and Install novelWriter](setup/BUILD.md) for more details.
-### Package Versions
+## Package Versions
Exporting to Markdown requires PyQt/Qt 5.14. There are no known minimum for `lxml`, but the code
was originally written with 4.2. The optional spell check library must be at least 3.0.0 to work
@@ -172,8 +165,15 @@ checker, but more can be added to the `nw/assets/dict` folder. See the [README](
file in that folder for how to generate more dictionaries. Note that the difflib-based option is
both slow and limited.
+## Debugging
-## Key Features
+If you need to debug novelWriter, you must run it from command line.
+It takes a few parameters, which can be listed with the switch `--help`.
+The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output
+for debugging.
+
+
+# Key Features
Some features of novelWriter are listed below. Consult the documentation for more information.
From 95db7faf8a74001ef71e20069eacbde2e7f17119 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 17:32:07 +0200
Subject: [PATCH 25/28] Fixed wrong heading type
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ce5cfdf0..41587d20 100644
--- a/README.md
+++ b/README.md
@@ -128,7 +128,7 @@ It comes with a lot of default dictionaries.
brew install enchant
```
-### Windows
+## Windows
On Windows, you may first need to install Python.
See the [python.org](https://www.python.org/) website for download packages.
From 3045275ab52163bf0b58d87cd3b1b2e90586a9c0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 17:45:34 +0200
Subject: [PATCH 26/28] Updated the main docstrings in the setup and make
scripts
---
make.py | 18 ++++++++++--------
setup.py | 20 +++++++++++++++++++-
2 files changed, 29 insertions(+), 9 deletions(-)
diff --git a/make.py b/make.py
index 15ed18ae..859ad8ca 100755
--- a/make.py
+++ b/make.py
@@ -1,15 +1,17 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
-This script will either build:
- * A single file executable named dist/novelWriter.exe. This is a quite
- slow option, and the file is fairly big. Option --onefile
- * A single directory named dist/novelWriter with a novelWriter.exe, and
- all dependecies included. This is the default.
- * The latter can be combined with a build stage of a setup.exe file
- named setup-novelwriter-.exe. Option --setup.
+This make script is intended for building distributable packages of
+novelWriter. These are either:
-In addition, providing the --pip flag will cause the script to try to
+ * A single file executable named dist/novelWriter(.exe). This is a
+ quite slow option, and the file is fairly big.
+ * A single directory named dist/novelWriter with a novelWriter(.exe),
+ and all dependecies included.
+ * The latter can be combined with a build stage of a setup.exe file if
+ on Windows. This requires Inno Setup to be installed and in path.
+
+In addition, providing the pip otion will cause the script to try to
install all dependencies needed for runing the build, and for running
novelWriter itself.
"""
diff --git a/setup.py b/setup.py
index 4683e3f2..c3c5710b 100755
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,22 @@
#!/usr/bin/env python3
+"""
+The main setup script for novelWeiter.
+
+It runs the standard setuptool.setup() with all options taken from the
+setup.cfg file.
+
+In addtion, a few speicalised commands are available:
+
+ * sample: Will build a sample.zip file, which is the way the sample project is
+ included into distributable packages.
+ * qthelp: Will build a QtAssistant readable version of the novelWriter
+ documentation. This should also be a part of distributed packages. It allows
+ for reading the help offline. Otherwise, the F1 button redirects to the
+ online documentation only.
+ * launcher: Will attempt to install novelWriter icons, mime type and create a
+ launcher for the application.
+
+"""
import os
import sys
import shutil
@@ -276,7 +294,7 @@ if __name__ == "__main__":
"This tool provides some additional setup commands for novelWriter.\n"
"\n"
"help Print the help message.\n"
- "gthelp Build the help documentation for use with the QtAssistant.\n"
+ "qthelp Build the help documentation for use with the QtAssistant.\n"
"sample Build the sample project as a zip file.\n"
"launcher Install launcher icons for freedesktop systems.\n"
)
From 4584a4b8b44f443d6e0d24c19b66506512bc17f8 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 23:35:33 +0200
Subject: [PATCH 27/28] Renamed setup/BUILD.md to setup/README.md
---
README.md | 4 ++--
setup/{BUILD.md => README.md} | 0
2 files changed, 2 insertions(+), 2 deletions(-)
rename setup/{BUILD.md => README.md} (100%)
diff --git a/README.md b/README.md
index 41587d20..9efa0b24 100644
--- a/README.md
+++ b/README.md
@@ -107,7 +107,7 @@ You can also create a launcher for running directly from source with:
sudo python3 setup.py launcher
```
-For more install options, see [Build and Install novelWriter](setup/BUILD.md).
+For more install options, see [Build and Install novelWriter](setup/README.md).
## macOS
@@ -150,7 +150,7 @@ C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py
```
You can also run the `make.py` script to generate a single executable, or an installer.
-See [Build and Install novelWriter](setup/BUILD.md) for more details.
+See [Build and Install novelWriter](setup/README.md) for more details.
## Package Versions
diff --git a/setup/BUILD.md b/setup/README.md
similarity index 100%
rename from setup/BUILD.md
rename to setup/README.md
From 50cc135dde7e9a14e017235ca6318a7e478df7a8 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 17 Oct 2020 23:46:54 +0200
Subject: [PATCH 28/28] Fixed lots of top-of-file docstrings that were out of
date
---
nw/constants/iso.py | 6 +++---
nw/core/spellcheck.py | 4 ++--
nw/core/tools.py | 15 ++++++++-------
nw/error.py | 2 +-
nw/gui/custom.py | 8 ++++----
nw/gui/outlinedetails.py | 8 ++++----
nw/gui/preferences.py | 8 ++++----
nw/gui/projload.py | 2 +-
nw/gui/projtree.py | 8 ++++----
nw/gui/theme.py | 8 ++++----
10 files changed, 35 insertions(+), 34 deletions(-)
diff --git a/nw/constants/iso.py b/nw/constants/iso.py
index 69bd6d23..ccdc0808 100644
--- a/nw/constants/iso.py
+++ b/nw/constants/iso.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
-"""novelWriter Language Codes
+"""novelWriter ISO Codes
- novelWriter – Language Codes
-==============================
+ novelWriter – ISO Codes
+=========================
Handles translating language codes to language names
File History:
diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py
index ff338f62..e3093070 100644
--- a/nw/core/spellcheck.py
+++ b/nw/core/spellcheck.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-"""novelWriter Spell Check Wrapper
+"""novelWriter Spell Check Classes
- novelWriter – Spell Check Wrapper
+ novelWriter – Spell Check Classes
===================================
Wrapper class for spell checking
diff --git a/nw/core/tools.py b/nw/core/tools.py
index 8062d591..ef9fbc6b 100644
--- a/nw/core/tools.py
+++ b/nw/core/tools.py
@@ -1,14 +1,15 @@
# -*- coding: utf-8 -*-
-"""novelWriter Word Counter
+"""novelWriter Various Tools
- novelWriter – Word Counter
-============================
- Simple word counter
+ novelWriter – Various Tools
+=============================
+ Various core tool functions
File History:
- Created: 2019-04-22 [0.0.1] countWords
- Created: 2019-10-13 [0.2.3] numberToWord, _numberToWordEN
- Merged: 2020-05-08 [0.4.5] All of the above into this file
+ Created: 2019-04-22 [0.0.1] countWords
+ Created: 2019-10-13 [0.2.3] numberToWord, _numberToWordEN
+ Merged: 2020-05-08 [0.4.5] All of the above into this file
+ Created: 2020-07-05 [0.10.0] numberToRoman
This file is a part of novelWriter
Copyright 2018–2020, Veronica Berglyd Olsen
diff --git a/nw/error.py b/nw/error.py
index a8f3c149..988b49e6 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-"""novelWriter Init
+"""novelWriter Exception Handling
novelWriter – Exception Handling
==================================
diff --git a/nw/gui/custom.py b/nw/gui/custom.py
index 2955c447..a6260f33 100644
--- a/nw/gui/custom.py
+++ b/nw/gui/custom.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-"""novelWriter Addition QConfigLayout
+"""novelWriter Custom Widgets and Layouts
- novelWriter – Addition QConfigLayout
-======================================
- A custom Qt grid layout for config forms similar to QFormLayout
+ novelWriter – Custom Widgets and Layouts
+==========================================
+ Various custom widget and layout classes
File History:
Created: 2020-05-03 [0.4.5] QConfigLayout
diff --git a/nw/gui/outlinedetails.py b/nw/gui/outlinedetails.py
index 4f6a56f5..18f2b90f 100644
--- a/nw/gui/outlinedetails.py
+++ b/nw/gui/outlinedetails.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-"""novelWriter GUI Project Outline
+"""novelWriter GUI Project Outline Details
- novelWriter – GUI Project Outline
-===================================
- Class holding the project outline view
+ novelWriter – GUI Project Outline Details
+===========================================
+ Class holding the project outline details view
File History:
Created: 2020-06-02 [0.7.0]
diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py
index 2a1cf653..7b8e0905 100644
--- a/nw/gui/preferences.py
+++ b/nw/gui/preferences.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-"""novelWriter GUI Config Editor
+"""novelWriter GUI Preferences
- novelWriter – GUI Config Editor
-=================================
- Class holding the config dialog
+ novelWriter – GUI Preferences
+===============================
+ Class holding the preferences dialog
File History:
Created: 2019-06-10 [0.1.5]
diff --git a/nw/gui/projload.py b/nw/gui/projload.py
index 8f18f7bb..29c9c9ab 100644
--- a/nw/gui/projload.py
+++ b/nw/gui/projload.py
@@ -3,7 +3,7 @@
novelWriter – GUI Open Project
================================
- New and open project dialog
+ The open project dialog
File History:
Created: 2020-02-26 [0.4.5]
diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py
index 6b1ee7bd..77fdf610 100644
--- a/nw/gui/projtree.py
+++ b/nw/gui/projtree.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-"""novelWriter GUI Document Tree
+"""novelWriter GUI Project Tree
- novelWriter – GUI Document Tree
-=================================
- Class holding the left side document tree view
+ novelWriter – GUI project Tree
+================================
+ Class holding the left side project tree view
File History:
Created: 2018-09-29 [0.0.1] GuiProjectTree
diff --git a/nw/gui/theme.py b/nw/gui/theme.py
index 5683d1a3..9d907a67 100644
--- a/nw/gui/theme.py
+++ b/nw/gui/theme.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-"""novelWriter Theme Class
+"""novelWriter Theme and Icons Classes
- novelWriter – Theme Class
-===========================
- This class reads and store the main theme
+ novelWriter – Theme and Icons Classs
+======================================
+ This class reads and stores the themes and the icons
File History:
Created: 2019-05-18 [0.1.3] GuiTheme