diff --git a/nw/core/item.py b/nw/core/item.py index 4dd9ddb9..0eb4ef26 100644 --- a/nw/core/item.py +++ b/nw/core/item.py @@ -53,10 +53,11 @@ class NWItem(): self.isExported = True # Document Meta Data - self.charCount = 0 - self.wordCount = 0 - self.paraCount = 0 - self.cursorPos = 0 + self.charCount = 0 # Current character count + self.wordCount = 0 # Current word count + self.paraCount = 0 # Current paragraph count + self.initCount = 0 # Initial word count + self.cursorPos = 0 # Last cursor position return @@ -274,4 +275,10 @@ class NWItem(): self.cursorPos = checkInt(thePosition, 0) return + def saveInitialCount(self): + """Set the initial word count. + """ + self.initCount = self.wordCount + return + # END Class NWItem diff --git a/nw/core/tree.py b/nw/core/tree.py index beb5d2e3..e786a727 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -134,6 +134,7 @@ class NWTree(): nwItem = NWItem(self.theProject) if nwItem.unpackXML(xItem): self.append(nwItem.itemHandle, nwItem.parHandle, nwItem) + nwItem.saveInitialCount() return True diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 09e4f5e4..5c23e513 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -36,8 +36,8 @@ from time import time from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp from PyQt5.QtGui import ( - QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, - QTextDocument, QCursor + QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, QIcon, + QTextDocument, QCursor, QPixmap ) from PyQt5.QtWidgets import ( qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, @@ -49,7 +49,7 @@ from nw.core import NWDoc, NWSpellSimple, countWords from nw.gui.dochighlight import GuiDocHighlighter from nw.common import transferCase from nw.constants import ( - nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwInsertSymbols + nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwInsertSymbols, nwItemClass ) logger = logging.getLogger(__name__) @@ -92,6 +92,7 @@ class GuiDocEditor(QTextEdit): # Document Title self.docHeader = GuiDocEditHeader(self) + self.docFooter = GuiDocEditFooter(self) self.docSearch = GuiDocEditSearch(self) # Syntax @@ -160,6 +161,7 @@ class GuiDocEditor(QTextEdit): self.setDocumentChanged(False) self.docHeader.setTitleFromHandle(self.theHandle) + self.docFooter.setHandle(self.theHandle) return True @@ -277,6 +279,7 @@ class GuiDocEditor(QTextEdit): self.setReadOnly(False) self.docHeader.setTitleFromHandle(self.theHandle) + self.docFooter.setHandle(self.theHandle) self.updateDocMargins() self.hLight.spellCheck = spTemp qApp.restoreOverrideCursor() @@ -342,7 +345,10 @@ class GuiDocEditor(QTextEdit): tB = self.frameWidth() tW = wW - 2*tB - sW tH = self.docHeader.height() + fH = self.docFooter.height() + fY = self.height() - fH - tB self.docHeader.setGeometry(tB, tB, tW, tH) + self.docFooter.setGeometry(tB, fY, tW, fH) if self.docSearch.isVisible(): rH = self.docSearch.height() @@ -352,16 +358,17 @@ class GuiDocEditor(QTextEdit): else: rH = 0 - self.setViewportMargins(tM, max(cM, tH, rH), tM, cM) + self.setViewportMargins(tM, max(cM, tH, rH), tM, max(cM, fH)) return - def updateDocTitle(self, tHandle): + def updateDocInfo(self, tHandle): """Called when an item label is changed to check if the document title bar needs updating, """ if tHandle == self.theHandle: self.docHeader.setTitleFromHandle(self.theHandle) + self.docFooter.updateInfo() self.updateDocMargins() return @@ -763,13 +770,13 @@ class GuiDocEditor(QTextEdit): self.nwDocument.theItem.setWordCount(self.wordCount) self.nwDocument.theItem.setParaCount(self.paraCount) - self.theParent.statusBar.setCounts(self.charCount, self.wordCount, self.paraCount) self.theParent.treeView.propagateCount(self.theHandle, self.wordCount) self.theParent.treeView.projectWordCount() self.theParent.treeMeta.updateCounts( self.theHandle, self.charCount, self.wordCount, self.paraCount ) self._checkDocSize(self.charCount) + self.docFooter.updateCounts() return @@ -1819,3 +1826,145 @@ class GuiDocEditHeader(QWidget): return # END Class GuiDocEditHeader + +# =============================================================================================== # +# The Embedded Document Footer +# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport +# =============================================================================================== # + +class GuiDocEditFooter(QWidget): + + def __init__(self, docEditor): + QWidget.__init__(self, docEditor) + + logger.debug("Initialising GuiDocEditFooter ...") + + self.mainConf = nw.CONFIG + self.docEditor = docEditor + self.theParent = docEditor.theParent + self.theProject = docEditor.theProject + self.theTheme = docEditor.theTheme + self.optState = docEditor.theProject.optState + self.theHandle = None + + # Make a QPalette that matches the Syntax Theme + self.thePalette = QPalette() + self.thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) + self.thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) + + self.sPx = int(round(0.9*self.theTheme.baseIconSize)) + fPx = int(0.9*self.theTheme.fontPixelSize) + bSp = self.mainConf.pxInt(4) + hSp = self.mainConf.pxInt(8) + + lblFont = self.font() + lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize) + + # Main Widget Settings + self.setContentsMargins(0, 0, 0, 0) + self.setAutoFillBackground(True) + self.setPalette(self.thePalette) + + buttonStyle = ( + "QToolButton {{border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" + ).format(*self.theTheme.colText) + + # Status + self.statusIcon = QLabel("") + self.statusIcon.setContentsMargins(0, 0, 0, 0) + self.statusIcon.setFixedHeight(self.sPx) + self.statusIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) + + self.statusText = QLabel("Status") + self.statusText.setIndent(0) + self.statusText.setMargin(0) + self.statusText.setContentsMargins(0, 0, 0, 0) + self.statusText.setAutoFillBackground(True) + self.statusText.setFixedHeight(fPx) + self.statusText.setAlignment(Qt.AlignLeft | Qt.AlignTop) + self.statusText.setPalette(self.thePalette) + self.statusText.setFont(lblFont) + + # Words + self.wordsIcon = QLabel("") + self.wordsIcon.setPixmap(self.theTheme.getPixmap("status_stats", (self.sPx, self.sPx))) + self.wordsIcon.setContentsMargins(0, 0, 0, 0) + self.wordsIcon.setFixedHeight(self.sPx) + self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) + + self.wordsText = QLabel("Words: 0") + self.wordsText.setIndent(0) + self.wordsText.setMargin(0) + self.wordsText.setContentsMargins(0, 0, 0, 0) + self.wordsText.setAutoFillBackground(True) + self.wordsText.setFixedHeight(fPx) + self.wordsText.setAlignment(Qt.AlignLeft | Qt.AlignTop) + self.wordsText.setPalette(self.thePalette) + self.wordsText.setFont(lblFont) + + # Assemble Layout + self.outerBox = QHBoxLayout() + self.outerBox.setSpacing(bSp) + self.outerBox.addWidget(self.statusIcon) + self.outerBox.addWidget(self.statusText) + self.outerBox.addStretch(1) + self.outerBox.addWidget(self.wordsIcon) + self.outerBox.addWidget(self.wordsText) + self.setLayout(self.outerBox) + + logger.debug("GuiDocEditFooter initialisation complete") + + return + + ## + # Methods + ## + + def setHandle(self, tHandle): + """Set the handle that will populate the footer's data. + """ + self.theHandle = tHandle + self.updateInfo() + self.updateCounts() + return + + def updateInfo(self): + """Update the content of text labels. + """ + nwItem = self.theProject.projTree[self.theHandle] + if nwItem is None: + sIcon = QPixmap() + sText = "" + else: + iStatus = nwItem.itemStatus + if nwItem.itemClass == nwItemClass.NOVEL: + iStatus = self.theProject.statusItems.checkEntry(iStatus) + theIcon = self.theParent.statusIcons[iStatus] + else: + iStatus = self.theProject.importItems.checkEntry(iStatus) + theIcon = self.theParent.importIcons[iStatus] + sIcon = theIcon.pixmap(self.sPx, self.sPx) + sText = nwItem.itemStatus + + self.statusIcon.setPixmap(sIcon) + self.statusText.setText(sText) + + return + + def updateCounts(self): + """Update the word counts. + """ + nwItem = self.theProject.projTree[self.theHandle] + if nwItem is None: + wCount = 0 + wDiff = 0 + else: + wCount = nwItem.wordCount + wDiff = wCount - nwItem.initCount + + self.wordsText.setText("Words: {:n} ({:+n})".format(wCount, wDiff)) + + return + +# END Class GuiDocEditFooter diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 17a7c079..863d2bf6 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -251,7 +251,7 @@ class GuiDocViewer(QTextBrowser): return - def updateDocTitle(self, tHandle): + def updateDocInfo(self, tHandle): """Called when an item label is changed to check if the document title bar needs updating, """ diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index c851a218..040125aa 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -51,9 +51,6 @@ class GuiMainStatus(QStatusBar): self.theTheme = theParent.theTheme self.refTime = None - self.charCount = 0 - self.wordCount = 0 - self.paraCount = 0 self.projWords = 0 self.sessWords = 0 @@ -134,7 +131,6 @@ class GuiMainStatus(QStatusBar): """ self.setRefTime(None) self.setStats(0, 0) - self.setCounts(0, 0, 0) self.setProjectStatus(None) self.setDocumentStatus(None) self._updateTime() @@ -186,15 +182,6 @@ class GuiMainStatus(QStatusBar): self._updateStats() return - def setCounts(self, cC, wC, pC): - """Set the current document statistics. - """ - self.charCount = cC - self.wordCount = wC - self.paraCount = pC - self._updateStats() - return - ## # Internal Functions ## @@ -203,12 +190,11 @@ class GuiMainStatus(QStatusBar): """Update statistics. """ self.statsText.setToolTip( - "D: Document word count
P: Project word count (session change)" + "Project word count (session change)" ) self.statsText.setText(( - "D:{wC:n} P:{pWC:n} ({sWC:+n})" + "Words: {pWC:n} ({sWC:+n})" ).format( - wC = self.wordCount, pWC = self.projWords, sWC = self.sessWords, )) diff --git a/nw/guimain.py b/nw/guimain.py index 091c659d..5c0714d4 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -676,8 +676,8 @@ class GuiMain(QMainWindow): if dlgProj.exec_(): self.treeView.setTreeItemValues(tHandle) self.treeMeta.updateViewBox(tHandle) - self.docEditor.updateDocTitle(tHandle) - self.docViewer.updateDocTitle(tHandle) + self.docEditor.updateDocInfo(tHandle) + self.docViewer.updateDocInfo(tHandle) return diff --git a/tests/test_gui.py b/tests/test_gui.py index d52c4250..6ee01405 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -391,7 +391,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): itemEdit._doClose() # Check that the header is updated - nwGUI.docEditor.updateDocTitle("31489056e0916") + nwGUI.docEditor.updateDocInfo("31489056e0916") assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel › New Chapter › Just a Page" assert not nwGUI.docEditor.setCursorLine("where?") assert nwGUI.docEditor.setCursorLine(2)