From 91d96b162ea35ab512490bae0d298bc4fe2141af Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 18 Jan 2021 17:52:32 +0100 Subject: [PATCH 1/2] Fixed and imrpoved margin calculations on GUI elements --- nw/core/tree.py | 11 ++++++-- nw/gui/dochighlight.py | 2 +- nw/gui/itemdetails.py | 60 +++++++++++++++++++++++++----------------- nw/gui/projdetails.py | 14 +++++----- nw/guimain.py | 36 ++++++++++++++----------- 5 files changed, 75 insertions(+), 48 deletions(-) diff --git a/nw/core/tree.py b/nw/core/tree.py index a82f8edc..36a56e5c 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -457,8 +457,13 @@ class NWTree(): return def _makeHandle(self, addSeed=""): - """Generate a unique item handle. In the unlikely event that the - key already exists, salt the seed and generate a new handle. + """Generate a unique item handle. In the event that the key + already exists, salt the seed and generate a new handle. + A key collision is very unlikely to be caused by the truncation + of the sha256 hash to 13 characters. Assuming it is near-random, + it will on average happen every 4.5^15 times. However, the clock + seed is likely to occasionally generate a collision if the + handle requests come faster than the clock resolution. """ if self._handleSeed is None: newSeed = str(time()) + addSeed @@ -466,11 +471,13 @@ class NWTree(): # This is used for debugging newSeed = str(self._handleSeed) self._handleSeed += 1 + logger.verbose("Generating handle with seed '%s'" % newSeed) itemHandle = sha256(newSeed.encode()).hexdigest()[0:13] if itemHandle in self._projTree: logger.warning("Duplicate handle encountered! Retrying ...") itemHandle = self._makeHandle(addSeed+"!") + return itemHandle # END Class NWTree diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 4e0ac4a0..81a4956f 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -390,7 +390,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): theFormat.setBackground(QBrush(fmtCol, Qt.SolidPattern)) if fmtSize is not None: - theFormat.setFontPointSize(round(fmtSize*self.mainConf.textSize)) + theFormat.setFontPointSize(int(round(fmtSize*self.mainConf.textSize))) return theFormat diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py index 923f509a..a5415939 100644 --- a/nw/gui/itemdetails.py +++ b/nw/gui/itemdetails.py @@ -49,24 +49,22 @@ class GuiItemDetails(QWidget): self.theTheme = theParent.theTheme self.theHandle = None - self.mainBox = QGridLayout(self) - self.mainBox.setVerticalSpacing(1) - self.mainBox.setHorizontalSpacing(6) - self.setLayout(self.mainBox) + # Sizes + hSp = self.mainConf.pxInt(6) + vSp = self.mainConf.pxInt(1) + mPx = self.mainConf.pxInt(6) + iPx = self.theTheme.baseIconSize + fPt = self.theTheme.fontPointSize - self.pS = 0.9*self.theTheme.fontPointSize - self.iPx = self.theTheme.baseIconSize - self.sPx = int(round(0.8*self.theTheme.baseIconSize)) - - self.expCheck = self.theTheme.getPixmap("check", (self.iPx, self.iPx)) - self.expCross = self.theTheme.getPixmap("cross", (self.iPx, self.iPx)) + self.expCheck = self.theTheme.getPixmap("check", (iPx, iPx)) + self.expCross = self.theTheme.getPixmap("cross", (iPx, iPx)) self.fntLabel = QFont() self.fntLabel.setBold(True) - self.fntLabel.setPointSizeF(self.pS) + self.fntLabel.setPointSizeF(0.9*fPt) self.fntValue = QFont() - self.fntValue.setPointSizeF(self.pS) + self.fntValue.setPointSizeF(0.9*fPt) # Label self.labelName = QLabel("Label") @@ -147,6 +145,7 @@ class GuiItemDetails(QWidget): self.pCountData.setAlignment(Qt.AlignRight) # Assemble + self.mainBox = QGridLayout(self) self.mainBox.addWidget(self.labelName, 0, 0, 1, 1) self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1) self.mainBox.addWidget(self.labelData, 0, 2, 1, 3) @@ -175,6 +174,12 @@ class GuiItemDetails(QWidget): self.mainBox.setColumnStretch(3, 0) self.mainBox.setColumnStretch(4, 0) + self.mainBox.setHorizontalSpacing(hSp) + self.mainBox.setVerticalSpacing(vSp) + self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx) + + self.setLayout(self.mainBox) + # Make sure the columns for flags and counts don't resize too often flagWidth = self.theTheme.getTextWidth("Mm", self.fntValue) countWidth = self.theTheme.getTextWidth("99,999", self.fntValue) @@ -193,26 +198,30 @@ class GuiItemDetails(QWidget): """Clear all the data values. """ self.labelFlag.setPixmap(QPixmap(1, 1)) - self.labelData.setText("") self.statusFlag.setPixmap(QPixmap(1, 1)) - self.statusData.setText("") self.classFlag.setText("") - self.classData.setText("") self.layoutFlag.setText("") - self.layoutData.setText("") + + self.labelData.setText("–") + self.statusData.setText("–") + self.classData.setText("–") + self.layoutData.setText("–") + self.cCountData.setText("–") self.wCountData.setText("–") self.pCountData.setText("–") + return def updateCounts(self, tHandle, cC, wC, pC): - """Just update the counts if the handle is the same as the one - we're already showing. + """Update the counts if the handle is the same as the one we're + already showing. Otherwise, do nothing. """ if tHandle == self.theHandle: self.cCountData.setText(f"{cC:n}") self.wCountData.setText(f"{wC:n}") self.pCountData.setText(f"{pC:n}") + return def updateViewBox(self, tHandle): @@ -229,13 +238,13 @@ class GuiItemDetails(QWidget): if len(theLabel) > 100: theLabel = theLabel[:96].rstrip()+" ..." - iStatus = nwItem.itemStatus + itStatus = nwItem.itemStatus if nwItem.itemClass == nwItemClass.NOVEL: - iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid - flagIcon = self.theParent.statusIcons[iStatus] + itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid + flagIcon = self.theParent.statusIcons[itStatus] else: - iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid - flagIcon = self.theParent.importIcons[iStatus] + itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid + flagIcon = self.theParent.importIcons[itStatus] if nwItem.itemType == nwItemType.FILE: if nwItem.isExported: @@ -244,8 +253,11 @@ class GuiItemDetails(QWidget): self.labelFlag.setPixmap(self.expCross) else: self.labelFlag.setPixmap(QPixmap(1, 1)) - self.statusFlag.setPixmap(flagIcon.pixmap(self.sPx, self.sPx)) + + iPx = int(round(0.8*self.theTheme.baseIconSize)) + self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx)) self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) + if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: self.layoutFlag.setText("-") else: diff --git a/nw/gui/projdetails.py b/nw/gui/projdetails.py index 716177af..1eb8fa99 100644 --- a/nw/gui/projdetails.py +++ b/nw/gui/projdetails.py @@ -57,7 +57,7 @@ class GuiProjectDetails(PagedDialog): self.setWindowTitle("Project Details") wW = self.mainConf.pxInt(600) - wH = self.mainConf.pxInt(425) + wH = self.mainConf.pxInt(400) self.setMinimumWidth(wW) self.setMinimumHeight(wH) @@ -137,6 +137,7 @@ class GuiProjectDetailsMain(QWidget): self.theIndex = theParent.theIndex fPx = self.theTheme.fontPixelSize + fPt = self.theTheme.fontPointSize vPx = self.mainConf.pxInt(4) hPx = self.mainConf.pxInt(12) @@ -145,7 +146,7 @@ class GuiProjectDetailsMain(QWidget): self.bookTitle = QLabel(self.theProject.bookTitle) bookFont = self.bookTitle.font() - bookFont.setPixelSize(round(2.2*fPx)) + bookFont.setPointSizeF(2.2*fPt) bookFont.setWeight(QFont.Bold) self.bookTitle.setFont(bookFont) self.bookTitle.setAlignment(Qt.AlignHCenter) @@ -153,7 +154,7 @@ class GuiProjectDetailsMain(QWidget): self.projName = QLabel("Working Title: %s" % self.theProject.projName) workFont = self.projName.font() - workFont.setPixelSize(round(0.8*fPx)) + workFont.setPointSizeF(0.8*fPt) workFont.setItalic(True) self.projName.setFont(workFont) self.projName.setAlignment(Qt.AlignHCenter) @@ -161,7 +162,7 @@ class GuiProjectDetailsMain(QWidget): self.bookAuthors = QLabel("By %s" % self.theProject.getAuthors()) authFont = self.bookAuthors.font() - authFont.setPixelSize(round(1.2*fPx)) + authFont.setPointSizeF(1.2*fPt) self.bookAuthors.setFont(authFont) self.bookAuthors.setAlignment(Qt.AlignHCenter) self.bookAuthors.setWordWrap(True) @@ -219,12 +220,13 @@ class GuiProjectDetailsMain(QWidget): # ======== self.outerBox = QVBoxLayout() + self.outerBox.addSpacing(fPx) self.outerBox.addWidget(self.bookTitle) self.outerBox.addWidget(self.projName) self.outerBox.addWidget(self.bookAuthors) - self.outerBox.addSpacing(round(2.5*fPx)) + self.outerBox.addSpacing(2*fPx) self.outerBox.addLayout(self.statsGrid) - self.outerBox.addSpacing(round(0.8*fPx)) + self.outerBox.addSpacing(fPx) self.outerBox.addStretch(1) self.outerBox.addLayout(self.projPathBox) diff --git a/nw/guimain.py b/nw/guimain.py index 8c6f8b68..96657628 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -95,6 +95,11 @@ class GuiMain(QMainWindow): # Build the GUI # ============= + # Sizes + mPx = self.mainConf.pxInt(4) + fPx = self.theTheme.fontPixelSize + fPt = self.theTheme.fontPointSize + # Main GUI Elements self.statusBar = GuiMainStatus(self) self.treeView = GuiProjectTree(self) @@ -107,24 +112,24 @@ class GuiMain(QMainWindow): self.projMeta = GuiOutlineDetails(self) self.mainMenu = GuiMainMenu(self) - # Minor Gui Elements + # Minor GUI Elements self.statusIcons = [] self.importIcons = [] # Project Tree Tabs self.projTabs = QTabWidget() self.projTabs.setTabPosition(QTabWidget.South) - self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};") + self.projTabs.setStyleSheet(r"QTabWidget::pane {border: 0;};") self.projTabs.addTab(self.treeView, "Project") self.projTabs.addTab(self.novelView, "Novel") self.projTabs.currentChanged.connect(self._projTabsChanged) tabFont = self.projTabs.tabBar().font() - tabFont.setPointSize(round(0.9*self.theTheme.fontPointSize)) + tabFont.setPointSizeF(0.9*fPt) self.projTabs.tabBar().setFont(tabFont) # Project Tree Action Buttons - btnSize = round(0.7*self.theTheme.fontPixelSize) + btnSize = int(round(0.7*fPx)) self.treeButtons = QToolBar() self.treeButtons.setToolButtonStyle(Qt.ToolButtonIconOnly) self.treeButtons.setIconSize(QSize(btnSize, btnSize)) @@ -151,7 +156,7 @@ class GuiMain(QMainWindow): self.treePane = QWidget() self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0, 0, 0, 0) - self.treeBox.setSpacing(0) + self.treeBox.setSpacing(mPx) self.treeBox.addWidget(self.projTabs) self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) @@ -176,26 +181,27 @@ class GuiMain(QMainWindow): # Main Tabs : Editor / Outline self.mainTabs = QTabWidget() self.mainTabs.setTabPosition(QTabWidget.East) - self.mainTabs.setStyleSheet("QTabWidget::pane {border: 0;}") + self.mainTabs.setStyleSheet(r"QTabWidget::pane {border: 0;}") self.mainTabs.addTab(self.splitDocs, "Editor") self.mainTabs.addTab(self.splitOutline, "Outline") self.mainTabs.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) + self.splitMain.setContentsMargins(mPx, mPx, mPx, mPx) self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.mainTabs) self.splitMain.setSizes(self.mainConf.getMainPanePos()) - # Indices of All Splitter Widgets - self.idxTree = self.splitMain.indexOf(self.treePane) - self.idxMain = self.splitMain.indexOf(self.mainTabs) - self.idxEditor = self.splitDocs.indexOf(self.docEditor) - self.idxViewer = self.splitDocs.indexOf(self.splitView) - self.idxViewDoc = self.splitView.indexOf(self.docViewer) - self.idxViewMeta = self.splitView.indexOf(self.viewMeta) + # Indices of Splitter Widgets + self.idxTree = self.splitMain.indexOf(self.treePane) + self.idxMain = self.splitMain.indexOf(self.mainTabs) + self.idxEditor = self.splitDocs.indexOf(self.docEditor) + self.idxViewer = self.splitDocs.indexOf(self.splitView) + self.idxViewDoc = self.splitView.indexOf(self.docViewer) + self.idxViewMeta = self.splitView.indexOf(self.viewMeta) + + # Indices of Tab Widgets self.idxTabEdit = self.mainTabs.indexOf(self.splitDocs) self.idxTabProj = self.mainTabs.indexOf(self.splitOutline) self.idxTreeView = self.projTabs.indexOf(self.treeView) From 92da9542298c0704629102d2a8d3a060e76a20eb Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 18 Jan 2021 18:55:32 +0100 Subject: [PATCH 2/2] Drop the showGUI debug flag, removing test mode from beyond the main function --- nw/__init__.py | 44 +++++++++++++++++++++++--------------------- nw/config.py | 1 - nw/guimain.py | 21 ++++++++++----------- tests/dummy.py | 3 +++ 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/nw/__init__.py b/nw/__init__.py index d8543237..dd8d2579 100644 --- a/nw/__init__.py +++ b/nw/__init__.py @@ -35,16 +35,16 @@ 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 1,2 : Major Version (01-ff) +# = Digit 3,4 : Minor Version (01-ff) +# - Digit 5,6 : Patch Version (01-ff) # = Digit 7 : Release Type (a: aplha, b: beta, c: candidate, f: final) -# - Digit 8 : Release Number (0-9) +# - Digit 8 : Release Number (0-f) # # Example : Full Short Description # ------------------------------------------------------------------------- @@ -55,7 +55,7 @@ from nw.config import Config # 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" __copyright__ = "Copyright 2018–2021, Veronica Berglyd Olsen" @@ -79,7 +79,7 @@ __credits__ = [ "Marian Lückhof (contributor, tester)" ] -# +## # Logging # ========= # Standard used for logging levels in novelWriter: @@ -90,7 +90,7 @@ __credits__ = [ # ----------- SPAM Threshold : Output above should be minimal ----------------- # DEBUG Use for descriptions of main program flow # VERBOSE Use for outputting values and program flow details -# +## # Add verbose logging level VERBOSE = 5 @@ -104,10 +104,9 @@ logging.Logger.verbose = logVerbose # Initiating logging logger = logging.getLogger(__name__) -# +## # Main Program -# ============== -# +## # Load the main config as a global object CONFIG = Config() @@ -142,15 +141,14 @@ def main(sysArgs=None): "GNU General Public License for more details.\n" "\n" "Usage:\n" - " -h, --help Print this message.\n" - " -v, --version Print program version and exit.\n" - " --info Print additional runtime information.\n" - " --debug Print debug output. Includes --info.\n" - " --verbose Increase verbosity of debug output. Includes --debug.\n" - " --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n" - " --config= Alternative config file.\n" - " --data= Alternative user data path.\n" - " --testmode Do not display GUI. Used by the test suite.\n" + " -h, --help Print this message.\n" + " -v, --version Print program version and exit.\n" + " --info Print additional runtime information.\n" + " --debug Print debug output. Includes --info.\n" + " --verbose Increase verbosity of debug output. Includes --debug.\n" + " --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n" + " --config= Alternative config file.\n" + " --data= Alternative user data path.\n" ).format( version = __version__, status = __status__, @@ -205,7 +203,6 @@ def main(sysArgs=None): testMode = True # Set Config Options - CONFIG.showGUI = not testMode CONFIG.debugInfo = debugLevel < logging.INFO CONFIG.cmdOpen = cmdOpen @@ -292,7 +289,12 @@ def main(sysArgs=None): # Connect the exception handler before making the main GUI sys.excepthook = exceptionHandler + # Launch main GUI nwGUI = GuiMain() + if not nwGUI.hasProject: + nwGUI.showProjectLoadDialog() + nwGUI.releaseNotes() + sys.exit(nwApp.exec_()) # END Function main diff --git a/nw/config.py b/nw/config.py index c1519f61..61a173f1 100644 --- a/nw/config.py +++ b/nw/config.py @@ -56,7 +56,6 @@ class Config: self.appHandle = self.appName.lower() # Debug Settings - self.showGUI = True # Allow blocking the GUI (disabled for testing) self.debugInfo = False # True if log level is DEBUG or VERBOSE # Config Error Handling diff --git a/nw/guimain.py b/nw/guimain.py index 96657628..e48987bc 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -275,20 +275,10 @@ class GuiMain(QMainWindow): logger.debug("GUI initialisation complete") - # Check if a project path was provided at command line, and if - # not, open the project manager instead. + # If a project path was provided at command line, open it if self.mainConf.cmdOpen is not None: logger.debug("Opening project from additional command line option") self.openProject(self.mainConf.cmdOpen) - else: - if self.mainConf.showGUI: - self.showProjectLoadDialog() - - # Show the latest release notes, if they haven't been shown before - if hexToInt(self.mainConf.lastNotes) < hexToInt(nw.__hexversion__): - if self.mainConf.showGUI: - self.showAboutNWDialog(showNotes=True) - self.mainConf.lastNotes = nw.__hexversion__ logger.debug("novelWriter is ready ...") self.setStatus("novelWriter is ready ...") @@ -322,6 +312,15 @@ class GuiMain(QMainWindow): self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000)) return True + def releaseNotes(self): + """Determine whether release notes need to be shown, and show + them by calling the About dialog. + """ + if hexToInt(self.mainConf.lastNotes) < hexToInt(nw.__hexversion__): + self.mainConf.lastNotes = nw.__hexversion__ + self.showAboutNWDialog(showNotes=True) + return + ## # Project Actions ## diff --git a/tests/dummy.py b/tests/dummy.py index daf0e070..9a74bb61 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -21,6 +21,9 @@ class DummyMain(): return + def releaseNotes(self): + return + def makeAlert(self, theMessage, theLevel): print("%s: %s" % (str(theLevel), theMessage)) self.lastAlert = str(theMessage)