Merge branch 'master' into editor_footer

This commit is contained in:
Veronica K. B. Olsen
2020-06-27 18:12:45 +02:00
40 changed files with 1661 additions and 629 deletions
+2 -2
View File
@@ -15,7 +15,7 @@ from nw.gui.preferences import GuiPreferences
from nw.gui.projload import GuiProjectLoad
from nw.gui.projsettings import GuiProjectSettings
from nw.gui.projtree import GuiProjectTree
from nw.gui.sessionlog import GuiSessionLogView
from nw.gui.sessionlog import GuiSessionLog
from nw.gui.statusbar import GuiMainStatus
from nw.gui.theme import GuiIcons, GuiTheme
@@ -36,7 +36,7 @@ __all__ = [
"GuiProjectLoad",
"GuiProjectSettings",
"GuiProjectTree",
"GuiSessionLogView",
"GuiSessionLog",
"GuiMainStatus",
"GuiIcons",
"GuiTheme",
+95 -12
View File
@@ -31,8 +31,9 @@ import nw
from os import path
from time import time
from datetime import datetime
from PyQt5.QtCore import Qt, QByteArray
from PyQt5.QtCore import Qt, QByteArray, QTimer
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtGui import (
QPalette, QColor, QTextDocumentWriter, QFont
@@ -43,6 +44,7 @@ from PyQt5.QtWidgets import (
QFileDialog, QFontDialog, QSpinBox
)
from nw.common import fuzzyTime
from nw.gui.custom import QSwitch
from nw.core import ToHtml
from nw.constants import (
@@ -76,6 +78,7 @@ class GuiBuildNovel(QDialog):
self.htmlText = [] # List of html document
self.htmlStyle = [] # List of html styles
self.nwdText = [] # List of markdown documents
self.buildTime = 0 # The timestamp of the last build
self.setWindowTitle("Build Novel Project")
self.setMinimumWidth(self.mainConf.pxInt(900))
@@ -411,11 +414,12 @@ class GuiBuildNovel(QDialog):
self.docView.clearStyleSheet()
else:
self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText)
self.docView.setContent(self.htmlText, self.buildTime)
else:
self.htmlText = []
self.htmlStyle = []
self.nwdText = []
self.buildTime = 0
return
@@ -509,6 +513,7 @@ class GuiBuildNovel(QDialog):
tEnd = time()
logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart)))
self.htmlStyle = makeHtml.getStyleSheet()
self.buildTime = tEnd
# Load the preview document with the html data
self.docView.setTextFont(textFont, textSize)
@@ -517,7 +522,7 @@ class GuiBuildNovel(QDialog):
self.docView.clearStyleSheet()
else:
self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText)
self.docView.setContent(self.htmlText, self.buildTime)
self._saveCache()
@@ -563,9 +568,6 @@ class GuiBuildNovel(QDialog):
def _saveDocument(self, theFormat):
"""Save the document to various formats.
"""
# FMT_PDF
byteFmt = QByteArray()
fileExt = ""
textFmt = ""
@@ -694,6 +696,7 @@ class GuiBuildNovel(QDialog):
"workingTitle" : self.theProject.projName,
"novelTitle" : self.theProject.bookTitle,
"authors" : self.theProject.bookAuthors,
"buildTime" : self.buildTime,
}
}
@@ -808,6 +811,8 @@ class GuiBuildNovel(QDialog):
if "nwdText" in theData.keys():
self.nwdText = theData["nwdText"]
dataCount += 1
if "buildTime" in theData.keys():
self.buildTime = theData["buildTime"]
return dataCount == 3
@@ -828,6 +833,7 @@ class GuiBuildNovel(QDialog):
"htmlText" : self.htmlText,
"htmlStyle" : self.htmlStyle,
"nwdText" : self.nwdText,
"buildTime" : self.buildTime,
}, indent=nIndent))
except Exception as e:
logger.error("Failed to save build cache")
@@ -871,8 +877,8 @@ class GuiBuildNovel(QDialog):
"section" : self.fmtSection.text().strip(),
})
winWidth = self.mainConf.pxInt(self.width())
winHeight = self.mainConf.pxInt(self.height())
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
justifyText = self.justifyText.isChecked()
noStyling = self.noStyling.isChecked()
textFont = self.textFont.text()
@@ -924,12 +930,14 @@ class GuiBuildNovelDocView(QTextBrowser):
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.theTheme = theParent.theTheme
self.buildTime = 0
self.setMinimumWidth(40*self.theParent.theTheme.textNWidth)
self.setOpenExternalLinks(False)
self.qDocument = self.document()
self.qDocument.setDocumentMargin(self.mainConf.textMargin)
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
self.setPlaceholderText(
"This area will show the content of the document to be "
"exported or printed. Press the \"Build Novel Project\" "
@@ -946,12 +954,35 @@ class GuiBuildNovelDocView(QTextBrowser):
docPalette = self.palette()
docPalette.setColor(QPalette.Base, QColor(255, 255, 255))
docPalette.setColor(QPalette.Text, QColor( 0, 0, 0))
docPalette.setColor(QPalette.Text, QColor(0, 0, 0))
self.setPalette(docPalette)
lblPalette = self.palette()
lblPalette.setColor(QPalette.Background, lblPalette.toolTipBase().color())
lblPalette.setColor(QPalette.Foreground, lblPalette.toolTipText().color())
lblFont = self.font()
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
fPx = int(1.1*self.theTheme.fontPixelSize)
mPx = self.mainConf.pxInt(4)
self.theTitle = QLabel("<b>Build Time:</b> Unknown", self)
self.theTitle.setIndent(0)
self.theTitle.setAutoFillBackground(True)
self.theTitle.setAlignment(Qt.AlignCenter)
self.theTitle.setFixedHeight(fPx)
self.theTitle.setPalette(lblPalette)
self.theTitle.setFont(lblFont)
self._updateDocMargins()
self.setStyleSheet()
self.show()
# Age Timer
self.ageTimer = QTimer()
self.ageTimer.setInterval(10000)
self.ageTimer.timeout.connect(self._updateBuildAge)
self.ageTimer.start()
logger.debug("GuiBuildNovelDocView initialisation complete")
@@ -977,15 +1008,20 @@ class GuiBuildNovelDocView(QTextBrowser):
self.setFont(theFont)
return
def setContent(self, theText):
def setContent(self, theText, timeStamp):
"""Set the content, either from text or list of text.
"""
if isinstance(theText, list):
theText = "".join(theText)
self.buildTime = timeStamp
theText = theText.replace("&emsp;", "&nbsp;"*4)
theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>")
theText = theText.replace("</del>", "</span>")
self.setHtml(theText)
self._updateBuildAge()
return
def setStyleSheet(self, theStyles=[]):
@@ -1007,4 +1043,51 @@ class GuiBuildNovelDocView(QTextBrowser):
self.qDocument.setDefaultStyleSheet("")
return
##
# Events
##
def resizeEvent(self, theEvent):
"""Make sure the document title is the same width as the window.
"""
QTextBrowser.resizeEvent(self, theEvent)
self._updateDocMargins()
return
##
# Internal Functions
##
def _updateBuildAge(self):
"""Update the build time and the fuzzy age.
"""
if self.buildTime > 0:
strBuildTime = "%s (%s)" % (
datetime.fromtimestamp(self.buildTime).strftime("%x %X"),
fuzzyTime(time() - self.buildTime)
)
else:
strBuildTime = "Unknown"
self.theTitle.setText("<b>Build Time:</b> %s" % strBuildTime)
def _updateDocMargins(self):
"""Automatically adjust the header to fill the top of the
document within the viewport.
"""
vBar = self.verticalScrollBar()
if vBar.isVisible():
sW = vBar.width()
else:
sW = 0
tB = self.frameWidth()
tW = self.width() - 2*tB - sW
tH = self.theTitle.height()
self.theTitle.setGeometry(tB, tB, tW, tH)
self.setViewportMargins(0, tH, 0, 0)
return
# END Class GuiBuildNovelDocView
+100 -4
View File
@@ -30,17 +30,17 @@
import logging
import nw
from PyQt5.QtGui import QColor, QPalette, QPainter
from PyQt5.QtGui import QColor, QPalette, QPainter, QFontMetrics
from PyQt5.QtCore import (
Qt, QRect, QPoint, QSize, QRectF, QPropertyAnimation, pyqtProperty
)
from PyQt5.QtWidgets import (
QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy,
QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle,
QStylePainter, QStyleOptionTab
QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle, QDialogButtonBox,
QStylePainter, QStyleOptionTab, QListWidget, QListWidgetItem, QFrame
)
from nw.constants import nwUnicode
from nw.constants import nwUnicode, nwQuotes
logger = logging.getLogger(__name__)
@@ -436,3 +436,99 @@ class VerticalTabBar(QTabBar):
return
# END Class VerticalTabBar
# =============================================================================================== #
# Quotes Dialog
# =============================================================================================== #
class QuotesDialog(QDialog):
def __init__(self, theParent=None, currentQuote="\""):
QDialog.__init__(self, parent=theParent)
self.mainConf = nw.CONFIG
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout()
self.selectedQuote = currentQuote
qMetrics = QFontMetrics(self.font())
pxW = 7*qMetrics.boundingRectChar("M").width()
pxH = 7*qMetrics.boundingRectChar("M").height()
pxH = 7*qMetrics.boundingRectChar("M").height()
lblFont = self.font()
lblFont.setPointSizeF(4*lblFont.pointSizeF())
# Preview Label
self.previewLabel = QLabel(currentQuote)
self.previewLabel.setFont(lblFont)
self.previewLabel.setFixedSize(QSize(pxW, pxH))
self.previewLabel.setAlignment(Qt.AlignCenter)
self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
# Quote Symbols
self.listBox = QListWidget()
self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
minSize = 100
for sKey, sLabel in nwQuotes.SYMBOLS.items():
theText = "[ %s ] %s" % (sKey, sLabel)
minSize = max(minSize, qMetrics.boundingRect(theText).width())
qtItem = QListWidgetItem(theText)
qtItem.setData(Qt.UserRole, sKey)
self.listBox.addItem(qtItem)
if sKey == currentQuote:
self.listBox.setCurrentItem(qtItem)
self.listBox.setMinimumWidth(minSize + self.mainConf.pxInt(40))
self.listBox.setMinimumHeight(self.mainConf.pxInt(150))
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doAccept)
self.buttonBox.rejected.connect(self._doReject)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
self.labelBox.addStretch(1)
self.innerBox.addLayout(self.labelBox)
self.innerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
return
##
# Slots
##
def _selectedSymbol(self):
"""Update the preview label and the selected quote style.
"""
selItems = self.listBox.selectedItems()
if selItems:
theSymbol = selItems[0].data(Qt.UserRole)
self.previewLabel.setText(theSymbol)
self.selectedQuote = theSymbol
return
def _doAccept(self):
"""Ok button clicked.
"""
self.accept()
return
def _doReject(self):
"""Cancel button clicked.
"""
self.reject()
return
# END Class QuotesDialog
+113 -54
View File
@@ -45,11 +45,12 @@ from PyQt5.QtWidgets import (
QFrame
)
from nw.core import NWDoc
from nw.core import NWDoc, NWSpellSimple, countWords
from nw.gui.dochighlight import GuiDocHighlighter
from nw.core import NWSpellSimple, countWords
from nw.constants import nwUnicode, nwDocAction, nwItemClass
from nw.common import transferCase
from nw.constants import (
nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwInsertSymbols
)
logger = logging.getLogger(__name__)
@@ -76,6 +77,7 @@ class GuiDocEditor(QTextEdit):
self.paraCount = 0
self.lastEdit = 0
self.bigDoc = False
self.doReplace = False
self.nonWord = "\"'"
# Typography
@@ -86,7 +88,6 @@ class GuiDocEditor(QTextEdit):
# Core Elements
self.qDocument = self.document()
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
self.qDocument.contentsChange.connect(self._docChange)
# Document Title
@@ -101,8 +102,7 @@ class GuiDocEditor(QTextEdit):
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu)
# Editor State
self.hasSelection = False
# Editor Settings
self.setMinimumWidth(self.mainConf.pxInt(300))
self.setAutoFillBackground(True)
self.setAcceptRichText(False)
@@ -157,8 +157,7 @@ class GuiDocEditor(QTextEdit):
self.paraCount = 0
self.lastEdit = 0
self.bigDoc = False
self.hasSelection = False
self.doReplace = False
self.setDocumentChanged(False)
self.docHeader.setTitleFromHandle(self.theHandle)
@@ -261,7 +260,9 @@ class GuiDocEditor(QTextEdit):
self.hLight.spellCheck = False
bfTime = time()
self._allowAutoReplace(False)
self.setPlainText(theDoc)
self._allowAutoReplace(True)
afTime = time()
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
@@ -356,7 +357,11 @@ class GuiDocEditor(QTextEdit):
else:
rH = 0
<<<<<<< HEAD
self.setViewportMargins(tM, max(cM, tH, rH), tM, max(cM, fH))
=======
self.setViewportMargins(tM, max(cM, tH, rH), tM, cM)
>>>>>>> master
return
@@ -392,8 +397,8 @@ class GuiDocEditor(QTextEdit):
"""
if self.mainConf.verQtValue >= 50900:
theText = self.qDocument.toRawText()
theText = theText.replace("\u2028", "\n") # Line separators
theText = theText.replace("\u2029", "\n") # Paragraph separators
theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
else:
theText = self.toPlainText()
return theText
@@ -500,6 +505,7 @@ class GuiDocEditor(QTextEdit):
if not self.theParent.hasProject:
logger.error("No project open")
return False
self._allowAutoReplace(False)
if theAction == nwDocAction.UNDO:
self.undo()
elif theAction == nwDocAction.REDO:
@@ -548,9 +554,15 @@ class GuiDocEditor(QTextEdit):
self._formatBlock(nwDocAction.BLOCK_COM)
elif theAction == nwDocAction.BLOCK_TXT:
self._formatBlock(nwDocAction.BLOCK_TXT)
elif theAction == nwDocAction.REPL_SNG:
self._replaceQuotes("'", self.typSQOpen, self.typSQClose)
elif theAction == nwDocAction.REPL_DBL:
self._replaceQuotes("\"", self.typDQOpen, self.typDQClose)
else:
logger.debug("Unknown or unsupported document action %s" % str(theAction))
self._allowAutoReplace(True)
return False
self._allowAutoReplace(True)
return True
def isEmpty(self):
@@ -574,6 +586,21 @@ class GuiDocEditor(QTextEdit):
))
return
def insertText(self, theInsert):
"""Insert a specific type of text at the cursor position.
"""
if isinstance(theInsert, str):
theText = theInsert
elif theInsert in nwInsertSymbols.SYMBOLS:
theText = nwInsertSymbols.SYMBOLS[theInsert]
else:
return False
theCursor = self.textCursor()
theCursor.beginEditBlock()
theCursor.insertText(theText)
theCursor.endEditBlock()
return True
def closeSearch(self):
"""Close the search box.
"""
@@ -586,34 +613,23 @@ class GuiDocEditor(QTextEdit):
def keyPressEvent(self, keyEvent):
"""Intercept key press events.
We need to intercept key presses briefly to record the state of
selection. This is in order to know whether we had a selection
prior to triggering the _docChange slot, as we do not want to
trigger autoreplace on selections. Autoreplace on selections
messes with undo/redo history.
We also need to intercept the Shift key modifier for certain key
combinations that modifies standard keys like enter and space.
However, we don't want to spend a lot of time in this function
as it is triggered on every keypress when typing.
We need to intercept a few key sequences:
* The return key redirects here even if the search box has
focus. Since we need the return key to continue search, we
block any further interaction here while it's in focus.
* The undo sequence bypasses the doAction pathway from the
menu, so we redirect it back from here.
* The default redo sequence is Ctrl+Shift+Z, which we don't
use, so we block it.
"""
self.hasSelection = self.textCursor().hasSelection()
if self.docSearch.searchBox.hasFocus():
# Block the event when the focus is on the search bar.
return
if keyEvent.modifiers() == Qt.ShiftModifier:
theKey = keyEvent.key()
if theKey == Qt.Key_Return:
self._insertHardBreak()
elif theKey == Qt.Key_Enter:
self._insertHardBreak()
elif theKey == Qt.Key_Space:
self._insertNonBreakingSpace()
else:
QTextEdit.keyPressEvent(self, keyEvent)
elif keyEvent == QKeySequence.Redo:
return
elif keyEvent == QKeySequence.Undo:
self.docAction(nwDocAction.UNDO)
else:
QTextEdit.keyPressEvent(self, keyEvent)
return
def mouseReleaseEvent(self, mEvent):
@@ -649,7 +665,7 @@ class GuiDocEditor(QTextEdit):
self.setDocumentChanged(True)
if not self.wcTimer.isActive():
self.wcTimer.start()
if self.mainConf.doReplace and not self.hasSelection:
if self.doReplace and charsAdded == 1:
self._docAutoReplace(self.qDocument.findBlock(thePos))
return
@@ -800,24 +816,6 @@ class GuiDocEditor(QTextEdit):
return True
def _insertHardBreak(self):
"""Inserts a hard line break at the cursor position.
"""
theCursor = self.textCursor()
theCursor.beginEditBlock()
theCursor.insertText(" \n")
theCursor.endEditBlock()
return
def _insertNonBreakingSpace(self):
"""Inserts a non-breaking space at the cursor position.
"""
theCursor = self.textCursor()
theCursor.beginEditBlock()
theCursor.insertText(nwUnicode.U_NBSP)
theCursor.endEditBlock()
return
def _openSpellContext(self):
"""Opens the spell check context menu at the current point of
the cursor.
@@ -879,6 +877,58 @@ class GuiDocEditor(QTextEdit):
return
def _replaceQuotes(self, sQuote, oQuote, cQuote):
"""Replace all straight quotes in the selected text.
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
posS = theCursor.selectionStart()
posE = theCursor.selectionEnd()
closeCheck = (
" ", "\n", nwUnicode.U_LSEP, nwUnicode.U_PSEP
)
self._allowAutoReplace(False)
for posC in range(posS, posE+1):
theCursor.setPosition(posC)
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
selText = theCursor.selectedText()
nS = len(selText)
if nS == 2:
pC = selText[0]
cC = selText[1]
elif nS == 1:
pC = " "
cC = selText[0]
else:
continue
if cC != sQuote:
continue
theCursor.clearSelection()
theCursor.setPosition(posC)
if pC in closeCheck:
theCursor.beginEditBlock()
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
theCursor.insertText(oQuote)
theCursor.endEditBlock()
else:
theCursor.beginEditBlock()
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
theCursor.insertText(cQuote)
theCursor.endEditBlock()
self._allowAutoReplace(True)
else:
self.theParent.makeAlert(
"Please selection some text before calling replace quotes.", nwAlert.ERROR
)
return
def _checkDocSize(self, theSize):
"""Check if document size crosses the big document limit set in
config. If so, we will set the big document flag to True.
@@ -1196,10 +1246,10 @@ class GuiDocEditor(QTextEdit):
self._beginSearch()
return
theCursor = self.textCursor()
if not theCursor.hasSelection():
return
theCursor = self.textCursor()
searchFor = self.docSearch.getSearchText()
replWith = self.docSearch.getReplaceText()
selText = theCursor.selectedText()
@@ -1242,6 +1292,15 @@ class GuiDocEditor(QTextEdit):
return
def _allowAutoReplace(self, theState):
"""used to enable/disable the auto-replace feature temporarily.
"""
if theState:
self.doReplace = self.mainConf.doReplace
else:
self.doReplace = False
return
# END Class GuiDocEditor
# =============================================================================================== #
@@ -1770,7 +1829,7 @@ class GuiDocEditHeader(QWidget):
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
self.theParent.treeView.setSelectedHandle(self.theHandle)
self.theParent.treeView.setSelectedHandle(self.theHandle, doScroll=True)
return
# END Class GuiDocEditHeader
+1 -1
View File
@@ -132,7 +132,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Non-Breaking Spaces
self.hRules.append((
"[%s]+" % nwUnicode.U_NBSP, {
"[%s%s]+" % (nwUnicode.U_NBSP, nwUnicode.U_THNBSP), {
0 : self.hStyles["nobreak"],
}
))
+1 -1
View File
@@ -120,7 +120,7 @@ class GuiDocMerge(QDialog):
self.theParent.treeView.revealTreeItem(nHandle)
theDoc.openDocument(nHandle, False)
theDoc.saveDocument(theText)
self.theParent.openDocument(nHandle)
self.theParent.openDocument(nHandle, doScroll=True)
self.close()
+8 -13
View File
@@ -60,6 +60,7 @@ class GuiDocViewer(QTextBrowser):
self.qDocument = self.document()
self.setMinimumWidth(self.mainConf.pxInt(300))
self.setAutoFillBackground(True)
self.setOpenExternalLinks(False)
self.initViewer()
@@ -104,11 +105,12 @@ class GuiDocViewer(QTextBrowser):
self.setFont(theFont)
docPalette = self.palette()
docPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
self.setPalette(docPalette)
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
self.qDocument.setDocumentMargin(0)
theOpt = QTextOption()
if self.mainConf.doJustify:
theOpt.setAlignment(Qt.AlignJustify)
@@ -242,19 +244,10 @@ class GuiDocViewer(QTextBrowser):
tH = self.docHeader.height()
fH = self.docFooter.height()
fY = self.height() - fH - tB
tT = cM - tH
bT = cM - fH
self.docHeader.setGeometry(tB, tB, tW, tH)
self.docFooter.setGeometry(tB, fY, tW, fH)
self.setViewportMargins(0, tH, 0, fH)
docFormat = self.qDocument.rootFrame().frameFormat()
docFormat.setTopMargin(max(0, tT))
docFormat.setBottomMargin(max(0, bT))
self.qDocument.blockSignals(True)
self.qDocument.rootFrame().setFrameFormat(docFormat)
self.qDocument.blockSignals(False)
self.setViewportMargins(cM, max(cM, tH), cM, max(cM, fH))
return
@@ -536,6 +529,8 @@ class GuiDocViewHeader(QWidget):
def _refreshDocument(self):
"""Reload the content of the document.
"""
if self.docViewer.theHandle == self.theParent.docEditor.theHandle:
self.theParent.saveDocument()
self.docViewer.reloadText()
return
@@ -547,7 +542,7 @@ class GuiDocViewHeader(QWidget):
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
self.theParent.treeView.setSelectedHandle(self.theHandle)
self.theParent.treeView.setSelectedHandle(self.theHandle, doScroll=True)
return
# END Class GuiDocViewHeader
+153 -55
View File
@@ -33,7 +33,7 @@ from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox
from nw.gui.about import GuiAbout
from nw.constants import nwItemType, nwItemClass, nwDocAction
from nw.constants import nwItemType, nwItemClass, nwDocAction, nwDocInsert
logger = logging.getLogger(__name__)
@@ -50,8 +50,9 @@ class GuiMainMenu(QMenuBar):
self._buildProjectMenu()
self._buildDocumentMenu()
self._buildEditMenu()
self._buildViewMenu()
self._buildInsertMenu()
self._buildFormatMenu()
self._buildViewMenu()
self._buildToolsMenu()
self._buildHelpMenu()
@@ -59,6 +60,7 @@ class GuiMainMenu(QMenuBar):
self._docAction = self.theParent.passDocumentAction
self._moveTreeItem = self.theParent.treeView.moveTreeItem
self._newTreeItem = self.theParent.treeView.newTreeItem
self._docInsert = self.theParent.docEditor.insertText
logger.debug("GuiMainMenu initialisation complete")
@@ -97,6 +99,8 @@ class GuiMainMenu(QMenuBar):
##
def _menuExit(self):
"""Exit novelWriter.
"""
self.theParent.closeMain()
return
@@ -123,19 +127,33 @@ class GuiMainMenu(QMenuBar):
return True
def _showAboutQt(self):
"""Show Qt's own About dialog.
"""
msgBox = QMessageBox()
msgBox.aboutQt(self.theParent,"About Qt")
return True
def _openHelp(self):
"""Open the documentation URL in the system's default browser.
"""
QDesktopServices.openUrl(QUrl(nw.__docurl__))
return True
def _openIssue(self):
"""Open the issue tracker URL in the system's default browser.
"""
QDesktopServices.openUrl(QUrl(nw.__issuesurl__))
return True
def _showDocumentLocation(self):
"""Open the dialog showing the location of the editor document.
"""
self.theParent.docEditor.revealLocation()
return True
def _doBackup(self):
"""Call the backup function for the project.
"""
self.theProject.zipIt(True)
return True
@@ -228,14 +246,14 @@ class GuiMainMenu(QMenuBar):
self.projMenu.addSeparator()
# Project > Edit
self.aEditItem = QAction("&Edit Project Item", self)
self.aEditItem = QAction("Edit Project Item", self)
self.aEditItem.setStatusTip("Change item settings")
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
self.aEditItem.triggered.connect(self.theParent.editItem)
self.projMenu.addAction(self.aEditItem)
# Project > Delete
self.aDeleteItem = QAction("&Delete Project Item", self)
self.aDeleteItem = QAction("Delete Project Item", self)
self.aDeleteItem.setStatusTip("Delete selected item")
self.aDeleteItem.setShortcut("Ctrl+Del")
self.aDeleteItem.triggered.connect(lambda : self.theParent.treeView.deleteItem(None))
@@ -265,21 +283,21 @@ class GuiMainMenu(QMenuBar):
self.docuMenu = self.addMenu("&Document")
# Document > New
self.aNewDoc = QAction("&New Document", self)
self.aNewDoc = QAction("New Document", self)
self.aNewDoc.setStatusTip("Create new document")
self.aNewDoc.setShortcut("Ctrl+N")
self.aNewDoc.triggered.connect(lambda : self._newTreeItem(nwItemType.FILE, None))
self.docuMenu.addAction(self.aNewDoc)
# Document > Open
self.aOpenDoc = QAction("&Open Document", self)
self.aOpenDoc = QAction("Open Document", self)
self.aOpenDoc.setStatusTip("Open selected document")
self.aOpenDoc.setShortcut("Ctrl+O")
self.aOpenDoc.triggered.connect(self.theParent.openSelectedItem)
self.docuMenu.addAction(self.aOpenDoc)
# Document > Save
self.aSaveDoc = QAction("&Save Document", self)
self.aSaveDoc = QAction("Save Document", self)
self.aSaveDoc.setStatusTip("Save current document")
self.aSaveDoc.setShortcut("Ctrl+S")
self.aSaveDoc.triggered.connect(self.theParent.saveDocument)
@@ -341,53 +359,6 @@ class GuiMainMenu(QMenuBar):
return
def _buildViewMenu(self):
# View
self.viewMenu = self.addMenu("&View")
# View > TreeView
self.aFocusTree = QAction("Focus Project Tree", self)
self.aFocusTree.setStatusTip("Move focus to project tree")
self.aFocusTree.setShortcut("Alt+1")
self.aFocusTree.triggered.connect(lambda : self.theParent.setFocus(1))
self.viewMenu.addAction(self.aFocusTree)
# View > Document Pane 1
self.aFocusEditor = QAction("Focus Document Editor", self)
self.aFocusEditor.setStatusTip("Move focus to left document pane")
self.aFocusEditor.setShortcut("Alt+2")
self.aFocusEditor.triggered.connect(lambda : self.theParent.setFocus(2))
self.viewMenu.addAction(self.aFocusEditor)
# View > Document Pane 2
self.aFocusView = QAction("Focus Document Viewer", self)
self.aFocusView.setStatusTip("Move focus to right document pane")
self.aFocusView.setShortcut("Alt+3")
self.aFocusView.triggered.connect(lambda : self.theParent.setFocus(3))
self.viewMenu.addAction(self.aFocusView)
# View > Separator
self.viewMenu.addSeparator()
# View > Toggle Distraction Free Mode
self.aZenMode = QAction("Zen Mode", self)
self.aZenMode.setStatusTip("Toggles distraction free mode, only showing text editor")
self.aZenMode.setShortcut("F8")
self.aZenMode.setCheckable(True)
self.aZenMode.setChecked(self.theParent.isZenMode)
self.aZenMode.toggled.connect(self.theParent.toggleZenMode)
self.viewMenu.addAction(self.aZenMode)
# View > Toggle Full Screen
self.aFullScreen = QAction("Full Screen Mode", self)
self.aFullScreen.setStatusTip("Maximises the main window")
self.aFullScreen.setShortcut("F11")
self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode)
self.viewMenu.addAction(self.aFullScreen)
return
def _buildEditMenu(self):
# Edit
@@ -497,6 +468,65 @@ class GuiMainMenu(QMenuBar):
return
def _buildInsertMenu(self):
# Insert
self.insertMenu = self.addMenu("&Insert")
# Insert > Short Dash
self.aInsENDash = QAction("Short Dash", self)
self.aInsENDash.setStatusTip("Insert short dash")
self.aInsENDash.setShortcut("Ctrl+K, -")
self.aInsENDash.triggered.connect(lambda: self._docInsert(nwDocInsert.SHORT_DASH))
self.insertMenu.addAction(self.aInsENDash)
# Insert > Long Dash
self.aInsEMDash = QAction("Long Dash", self)
self.aInsEMDash.setStatusTip("Insert long dash")
self.aInsEMDash.setShortcut("Ctrl+K, _")
self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwDocInsert.LONG_DASH))
self.insertMenu.addAction(self.aInsEMDash)
# Insert > Ellipsis
self.aInsEllipsis = QAction("Ellipsis", self)
self.aInsEllipsis.setStatusTip("Insert ellipsis")
self.aInsEllipsis.setShortcut("Ctrl+K, .")
self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwDocInsert.ELLIPSIS))
self.insertMenu.addAction(self.aInsEllipsis)
# Insert > Separator
self.insertMenu.addSeparator()
# Insert > Hard Line Break
self.aInsHardBreak = QAction("Hard Line Break", self)
self.aInsHardBreak.setStatusTip("Insert a hard line break")
self.aInsHardBreak.setShortcut("Ctrl+K, Return")
self.aInsHardBreak.triggered.connect(lambda: self._docInsert(nwDocInsert.HARD_BREAK))
self.insertMenu.addAction(self.aInsHardBreak)
# Insert > Non-Breaking Space
self.aInsNBSpace = QAction("Non-Breaking Space", self)
self.aInsNBSpace.setStatusTip("Insert a non-breaking space")
self.aInsNBSpace.setShortcut("Ctrl+K, Space")
self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.NB_SPACE))
self.insertMenu.addAction(self.aInsNBSpace)
# Insert > Thin Space
self.aInsThinSpace = QAction("Thin Space", self)
self.aInsThinSpace.setStatusTip("Insert a thin space")
self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space")
self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.THIN_SPACE))
self.insertMenu.addAction(self.aInsThinSpace)
# Insert > Thin Non-Breaking Space
self.aInsThinNBSpace = QAction("Thin Non-Breaking Space", self)
self.aInsThinNBSpace.setStatusTip("Insert a thin non-breaking space")
self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space")
self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.THIN_NB_SPACE))
self.insertMenu.addAction(self.aInsThinNBSpace)
return
def _buildFormatMenu(self):
# Format
@@ -547,7 +577,7 @@ class GuiMainMenu(QMenuBar):
self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
self.fmtMenu.addAction(self.aFmtSQuote)
# Edit > Separator
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Header 1
@@ -592,6 +622,68 @@ class GuiMainMenu(QMenuBar):
self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
self.fmtMenu.addAction(self.aFmtNoFormat)
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Replace Single Quotes
self.aFmtReplSng = QAction("Replace Single Quotes", self)
self.aFmtReplSng.setStatusTip("Replace all straight single quotes in selected text")
self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
self.fmtMenu.addAction(self.aFmtReplSng)
# Format > Replace Double Quotes
self.aFmtReplDbl = QAction("Replace Double Quotes", self)
self.aFmtReplDbl.setStatusTip("Replace all straight double quotes in selected text")
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
self.fmtMenu.addAction(self.aFmtReplDbl)
return
def _buildViewMenu(self):
# View
self.viewMenu = self.addMenu("&View")
# View > TreeView
self.aFocusTree = QAction("Focus Project Tree", self)
self.aFocusTree.setStatusTip("Move focus to project tree")
self.aFocusTree.setShortcut("Alt+1")
self.aFocusTree.triggered.connect(lambda : self.theParent.setFocus(1))
self.viewMenu.addAction(self.aFocusTree)
# View > Document Pane 1
self.aFocusEditor = QAction("Focus Document Editor", self)
self.aFocusEditor.setStatusTip("Move focus to left document pane")
self.aFocusEditor.setShortcut("Alt+2")
self.aFocusEditor.triggered.connect(lambda : self.theParent.setFocus(2))
self.viewMenu.addAction(self.aFocusEditor)
# View > Document Pane 2
self.aFocusView = QAction("Focus Document Viewer", self)
self.aFocusView.setStatusTip("Move focus to right document pane")
self.aFocusView.setShortcut("Alt+3")
self.aFocusView.triggered.connect(lambda : self.theParent.setFocus(3))
self.viewMenu.addAction(self.aFocusView)
# View > Separator
self.viewMenu.addSeparator()
# View > Toggle Distraction Free Mode
self.aZenMode = QAction("Zen Mode", self)
self.aZenMode.setStatusTip("Toggles distraction free mode, only showing text editor")
self.aZenMode.setShortcut("F8")
self.aZenMode.setCheckable(True)
self.aZenMode.setChecked(self.theParent.isZenMode)
self.aZenMode.toggled.connect(self.theParent.toggleZenMode)
self.viewMenu.addAction(self.aZenMode)
# View > Toggle Full Screen
self.aFullScreen = QAction("Full Screen Mode", self)
self.aFullScreen.setStatusTip("Maximises the main window")
self.aFullScreen.setShortcut("F11")
self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode)
self.viewMenu.addAction(self.aFullScreen)
return
def _buildToolsMenu(self):
@@ -703,6 +795,12 @@ class GuiMainMenu(QMenuBar):
self.aHelp.triggered.connect(self._openHelp)
self.helpMenu.addAction(self.aHelp)
# Document > Report Issue
self.aIssue = QAction("Report an Issue", self)
self.aIssue.setStatusTip("View online documentation")
self.aIssue.triggered.connect(self._openIssue)
self.helpMenu.addAction(self.aIssue)
return
# END Class GuiMainMenu
+1 -1
View File
@@ -196,7 +196,7 @@ class GuiOutline(QTreeWidget):
except:
tLine = 1
logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine))
self.theParent.openDocument(tHandle, tLine - 1)
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
return
def _itemSelected(self):
+63 -37
View File
@@ -37,9 +37,9 @@ from PyQt5.QtWidgets import (
QDialogButtonBox, QFileDialog, QFontDialog
)
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog
from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
from nw.constants import nwAlert, nwQuotes
from nw.constants import nwQuotes
logger = logging.getLogger(__name__)
@@ -816,51 +816,72 @@ class GuiConfigEditAutoReplaceTab(QWidget):
self.mainForm.addGroupLabel("Quotation Style")
qWidth = self.mainConf.pxInt(40)
bWidth = int(2.5*self.theTheme.getTextWidth("..."))
## Single Quote Style
self.quoteSingleStyleO = QLineEdit()
self.quoteSingleStyleO.setMaxLength(1)
self.quoteSingleStyleO.setReadOnly(True)
self.quoteSingleStyleO.setFixedWidth(qWidth)
self.quoteSingleStyleO.setAlignment(Qt.AlignCenter)
self.quoteSingleStyleO.setText(self.mainConf.fmtSingleQuotes[0])
self.btnSingleStyleO = QPushButton("...")
self.btnSingleStyleO.setMaximumWidth(bWidth)
self.btnSingleStyleO.clicked.connect(self._getSingleOpen)
self.mainForm.addRow(
"Single quote open style",
self.quoteSingleStyleO,
"Auto-replaces apostrophe before words."
"Auto-replaces apostrophe before words.",
theButton=self.btnSingleStyleO
)
self.quoteSingleStyleC = QLineEdit()
self.quoteSingleStyleC.setMaxLength(1)
self.quoteSingleStyleC.setReadOnly(True)
self.quoteSingleStyleC.setFixedWidth(qWidth)
self.quoteSingleStyleC.setAlignment(Qt.AlignCenter)
self.quoteSingleStyleC.setText(self.mainConf.fmtSingleQuotes[1])
self.btnSingleStyleC = QPushButton("...")
self.btnSingleStyleC.setMaximumWidth(bWidth)
self.btnSingleStyleC.clicked.connect(self._getSingleClose)
self.mainForm.addRow(
"Single quote close style",
self.quoteSingleStyleC,
"Auto-replaces apostrophe after words."
"Auto-replaces apostrophe after words.",
theButton=self.btnSingleStyleC
)
## Double Quote Style
self.quoteDoubleStyleO = QLineEdit()
self.quoteDoubleStyleO.setMaxLength(1)
self.quoteDoubleStyleO.setReadOnly(True)
self.quoteDoubleStyleO.setFixedWidth(qWidth)
self.quoteDoubleStyleO.setAlignment(Qt.AlignCenter)
self.quoteDoubleStyleO.setText(self.mainConf.fmtDoubleQuotes[0])
self.btnDoubleStyleO = QPushButton("...")
self.btnDoubleStyleO.setMaximumWidth(bWidth)
self.btnDoubleStyleO.clicked.connect(self._getDoubleOpen)
self.mainForm.addRow(
"Double quote open style",
self.quoteDoubleStyleO,
"Auto-replaces straight quotes before words."
"Auto-replaces straight quotes before words.",
theButton=self.btnDoubleStyleO
)
self.quoteDoubleStyleC = QLineEdit()
self.quoteDoubleStyleC.setMaxLength(1)
self.quoteDoubleStyleC.setReadOnly(True)
self.quoteDoubleStyleC.setFixedWidth(qWidth)
self.quoteDoubleStyleC.setAlignment(Qt.AlignCenter)
self.quoteDoubleStyleC.setText(self.mainConf.fmtDoubleQuotes[1])
self.btnDoubleStyleC = QPushButton("...")
self.btnDoubleStyleC.setMaximumWidth(bWidth)
self.btnDoubleStyleC.clicked.connect(self._getDoubleClose)
self.mainForm.addRow(
"Double quote close style",
self.quoteDoubleStyleC,
"Auto-replaces straight quotes after words."
"Auto-replaces straight quotes after words.",
theButton=self.btnDoubleStyleC
)
return
@@ -889,37 +910,10 @@ class GuiConfigEditAutoReplaceTab(QWidget):
fmtDoubleQuotesO = self.quoteDoubleStyleO.text()
fmtDoubleQuotesC = self.quoteDoubleStyleC.text()
if self._checkQuoteSymbol(fmtSingleQuotesO):
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtSingleQuotesO, nwAlert.ERROR
)
validEntries = False
if self._checkQuoteSymbol(fmtSingleQuotesC):
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtSingleQuotesC, nwAlert.ERROR
)
validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesO):
self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtDoubleQuotesO, nwAlert.ERROR
)
validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesC):
self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtDoubleQuotesC, nwAlert.ERROR
)
validEntries = False
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO
self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC
self.mainConf.confChanged = True
@@ -939,6 +933,38 @@ class GuiConfigEditAutoReplaceTab(QWidget):
self.autoReplaceDots.setEnabled(theState)
return
def _getSingleOpen(self):
"""Dialog for single quote open.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteSingleStyleO.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteSingleStyleO.setText(qtBox.selectedQuote)
return
def _getSingleClose(self):
"""Dialog for single quote close.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteSingleStyleC.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteSingleStyleC.setText(qtBox.selectedQuote)
return
def _getDoubleOpen(self):
"""Dialog for double quote open.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteDoubleStyleO.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteDoubleStyleO.setText(qtBox.selectedQuote)
return
def _getDoubleClose(self):
"""Dialog for double quote close.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteDoubleStyleC.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteDoubleStyleC.setText(qtBox.selectedQuote)
return
##
# Internal Functions
##
+39 -11
View File
@@ -31,10 +31,9 @@ import nw
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QLineEdit, QPlainTextEdit,
QLabel, QWidget, QTabWidget, QDialogButtonBox, QListWidget, QPushButton,
QListWidgetItem, QColorDialog, QAbstractItemView, QTreeWidget, QCheckBox,
QTreeWidgetItem
QHBoxLayout, QVBoxLayout, QGridLayout, QLineEdit, QPlainTextEdit, QLabel,
QWidget, QDialogButtonBox, QListWidget, QPushButton, QListWidgetItem,
QColorDialog, QAbstractItemView, QTreeWidget, QTreeWidgetItem
)
from nw.constants import nwAlert
@@ -52,9 +51,17 @@ class GuiProjectSettings(PagedDialog):
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.optState = theProject.optState
self.theProject.countStatus()
self.setWindowTitle("Project Settings")
self.setMinimumWidth(self.mainConf.pxInt(570))
self.setMinimumHeight(self.mainConf.pxInt(355))
self.resize(
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winWidth", 570)),
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winHeight", 355))
)
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
self.tabMeta = GuiProjectEditMeta(self.theParent, self.theProject)
@@ -73,13 +80,17 @@ class GuiProjectSettings(PagedDialog):
self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox)
self.show()
logger.debug("GuiProjectSettings initialisation complete")
return
##
# Slots
##
def _doSave(self):
"""Save settings and close dialog.
"""
logger.verbose("GuiProjectSettings save button clicked")
projName = self.tabMain.editName.text()
@@ -103,13 +114,23 @@ class GuiProjectSettings(PagedDialog):
newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList)
self.close()
self._doClose()
return
def _doClose(self):
logger.verbose("GuiProjectSettings close button clicked")
"""Close the dialog.
"""
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
replaceColW = self.mainConf.rpxInt(self.tabReplace.listBox.columnWidth(0))
self.optState.setValue("GuiProjectSettings", "winWidth", winWidth)
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
self.close()
return
# END Class GuiProjectSettings
@@ -455,16 +476,23 @@ class GuiProjectEditReplace(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theProject
self.optState = theProject.optState
self.arChanged = False
self.outerBox = QVBoxLayout()
self.bottomBox = QHBoxLayout()
self.listBox = QTreeWidget()
self.outerBox = QVBoxLayout()
self.bottomBox = QHBoxLayout()
wCol0 = self.mainConf.pxInt(
self.optState.getInt("GuiProjectSettings", "replaceColW", 100)
)
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Keyword","Replace With"])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(0, wCol0)
self.listBox.setIndentation(0)
for aKey, aVal in self.theProject.autoReplace.items():
+27 -22
View File
@@ -54,6 +54,7 @@ class GuiProjectTree(QTreeWidget):
QTreeWidget.__init__(self, theParent)
logger.debug("Initialising GuiProjectTree ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
@@ -237,8 +238,8 @@ class GuiProjectTree(QTreeWidget):
"""
if qApp.focusWidget() == self and self.theParent.hasProject:
tHandle = self.getSelectedHandle()
tItem = self._getTreeItem(tHandle)
pItem = tItem.parent()
tItem = self._getTreeItem(tHandle)
pItem = tItem.parent()
if pItem is None:
tIndex = self.indexOfTopLevelItem(tItem)
nChild = self.topLevelItemCount()
@@ -367,6 +368,7 @@ class GuiProjectTree(QTreeWidget):
if nwItemS is None:
return False
wCount = int(trItemS.text(self.C_COUNT))
if nwItemS.itemType == nwItemType.FILE:
logger.debug("User requested file %s moved to trash" % tHandle)
trItemP = trItemS.parent()
@@ -393,7 +395,8 @@ class GuiProjectTree(QTreeWidget):
if doPermanent:
logger.debug("Permanently deleting file with handle %s" % tHandle)
tIndex = trItemP.indexOfChild(trItemS)
self.propagateCount(tHandle, 0)
tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
if self.theParent.docEditor.theHandle == tHandle:
@@ -422,10 +425,12 @@ class GuiProjectTree(QTreeWidget):
if pHandle is None:
logger.warning("File has no parent item")
self.propagateCount(tHandle, 0)
tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
trItemT.addChild(trItemC)
nwItemS.setParent(self.theProject.projTree.trashRoot())
self.propagateCount(tHandle, wCount)
self._setTreeChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
@@ -504,7 +509,7 @@ class GuiProjectTree(QTreeWidget):
"""
tItem = self._getTreeItem(tHandle)
if tItem is not None:
tItem.setText(self.C_COUNT,str(theCount))
tItem.setText(self.C_COUNT, str(theCount))
pItem = tItem.parent()
if pItem is not None:
pCount = 0
@@ -520,7 +525,7 @@ class GuiProjectTree(QTreeWidget):
relevant values in the project and on the status bar. This call
is a fast way of getting this number, and depends on the
propagateCount function being called when it should to maintain
the correct count.
the correct count. Orphan folder is not included in the total.
"""
nWords = 0
for n in range(self.topLevelItemCount()):
@@ -569,14 +574,14 @@ class GuiProjectTree(QTreeWidget):
selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole))
return selHandles
def setSelectedHandle(self, tHandle):
def setSelectedHandle(self, tHandle, doScroll=False):
"""Set a specific handle as the selected item.
"""
if tHandle in self.theMap:
self.clearSelection()
self.theMap[tHandle].setSelected(True)
selItems = self.selectedIndexes()
if selItems:
if selItems and doScroll:
self.scrollTo(
selItems[0], QAbstractItemView.PositionAtCenter
)
@@ -629,23 +634,26 @@ class GuiProjectTree(QTreeWidget):
logger.error("Invalid drop index")
return
dItem = self.itemFromIndex(dIndex)
sItem = self._getTreeItem(sHandle)
dItem = self.itemFromIndex(dIndex)
dHandle = dItem.data(self.C_NAME, Qt.UserRole)
snItem = self.theProject.projTree[sHandle]
dnItem = self.theProject.projTree[dHandle]
snItem = self.theProject.projTree[sHandle]
dnItem = self.theProject.projTree[dHandle]
if dnItem is None:
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
return
isSame = snItem.itemClass == dnItem.itemClass
isNone = snItem.itemClass == nwItemClass.NO_CLASS
isNote = snItem.itemLayout == nwItemLayout.NOTE
onFile = dnItem.itemType == nwItemType.FILE
isRoot = snItem.itemType == nwItemType.ROOT
onRoot = dnItem.itemType == nwItemType.ROOT
wCount = int(sItem.text(self.C_COUNT))
isSame = snItem.itemClass == dnItem.itemClass
isNone = snItem.itemClass == nwItemClass.NO_CLASS
isNote = snItem.itemLayout == nwItemLayout.NOTE
onFile = dnItem.itemType == nwItemType.FILE
isRoot = snItem.itemType == nwItemType.ROOT
onRoot = dnItem.itemType == nwItemType.ROOT
isOnTop = self.dropIndicatorPosition() == QAbstractItemView.OnItem
if (isSame or isNone or isNote) and not (onFile and isOnTop) and not isRoot:
logger.debug("Drag'n'drop of item %s accepted" % sHandle)
self.propagateCount(sHandle, 0)
QTreeWidget.dropEvent(self, theEvent)
if isNone:
self._moveOrphanedItem(sHandle, dHandle)
@@ -660,6 +668,7 @@ class GuiProjectTree(QTreeWidget):
))
snItem.setClass(dnItem.itemClass)
self.setTreeItemValues(sHandle)
self.propagateCount(sHandle, wCount)
else:
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
@@ -794,8 +803,7 @@ class GuiProjectTree(QTreeWidget):
def _updateItemParent(self, tHandle):
"""Update the parent handle of an item so that the information
in the project is consistent with the treeView. Also move the
word count over to the new parent tree.
in the project is consistent with the treeView.
"""
trItemS = self._getTreeItem(tHandle)
nwItemS = self.theProject.projTree[tHandle]
@@ -805,10 +813,7 @@ class GuiProjectTree(QTreeWidget):
return False
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
wC = int(trItemS.text(self.C_COUNT))
self.propagateCount(tHandle, -wC)
nwItemS.setParent(pHandle)
self.propagateCount(tHandle, wC)
self.setTreeItemValues(tHandle)
self._setTreeChanged(True)
@@ -929,7 +934,7 @@ class GuiProjectTreeMenu(QMenu):
"""Forward the open document call to the main GUI window.
"""
if self.theItem is not None:
self.theTree.theParent.openDocument(self.theItem.itemHandle)
self.theTree.theParent.openDocument(self.theItem.itemHandle, doScroll=False)
return
def _doViewItem(self):
+407 -110
View File
@@ -26,78 +26,105 @@
"""
import logging
import json
import nw
from os import path
from datetime import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QDialogButtonBox,
QGridLayout, QLabel, QGroupBox, QCheckBox
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
QLabel, QGroupBox, QMenu, QAction, QFileDialog
)
from nw.constants import nwConst, nwFiles, nwAlert
from nw.gui.custom import QSwitch
logger = logging.getLogger(__name__)
class GuiSessionLogView(QDialog):
class GuiSessionLog(QDialog):
C_TIME = 0
C_LENGTH = 1
C_COUNT = 2
C_BAR = 3
FMT_JSON = 0
FMT_CSV = 1
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising SessionLogView ...")
logger.debug("Initialising GuiSessionLog ...")
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.optState = self.theProject.optState
self.theProject = theProject
self.theTheme = theParent.theTheme
self.optState = theProject.optState
self.logData = []
self.filterData = []
self.timeFilter = 0.0
self.timeTotal = 0.0
self.outerBox = QGridLayout()
self.bottomBox = QHBoxLayout()
self.wordOffset = 0
self.setWindowTitle("Session Log")
self.setMinimumWidth(self.mainConf.pxInt(420))
self.setMinimumHeight(self.mainConf.pxInt(400))
self.resize(
self.mainConf.pxInt(self.optState.getInt("GuiSessionLog", "winWidth", 550)),
self.mainConf.pxInt(self.optState.getInt("GuiSessionLog", "winHeight", 500))
)
wCol0 = self.mainConf.pxInt(self.optState.getInt("GuiSession", "widthCol0", 180))
wCol1 = self.mainConf.pxInt(self.optState.getInt("GuiSession", "widthCol1", 80))
wCol2 = self.mainConf.pxInt(self.optState.getInt("GuiSession", "widthCol2", 80))
# List Box
wCol0 = self.mainConf.pxInt(
self.optState.getInt("GuiSessionLog", "widthCol0", 180)
)
wCol1 = self.mainConf.pxInt(
self.optState.getInt("GuiSessionLog", "widthCol1", 80)
)
wCol2 = self.mainConf.pxInt(
self.optState.getInt("GuiSessionLog", "widthCol2", 80)
)
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Session Start","Length","Words",""])
self.listBox.setHeaderLabels(["Session Start","Length","Words","Histogram"])
self.listBox.setIndentation(0)
self.listBox.setColumnWidth(0, wCol0)
self.listBox.setColumnWidth(1, wCol1)
self.listBox.setColumnWidth(2, wCol2)
self.listBox.setColumnWidth(3, 0)
self.listBox.setColumnWidth(self.C_TIME, wCol0)
self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
self.listBox.setColumnWidth(self.C_COUNT, wCol2)
hHeader = self.listBox.headerItem()
hHeader.setTextAlignment(1,Qt.AlignRight)
hHeader.setTextAlignment(2,Qt.AlignRight)
hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
self.monoFont = QFont("Monospace", 10)
self.monoFont = QFont()
self.monoFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
self.monoFont.setFamily(self.theTheme.guiFontFixed.family())
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder)
sortCol = self.optState.validIntRange(
self.optState.getInt("GuiSession", "sortCol", 0), 0, 2, 0
self.optState.getInt("GuiSessionLog", "sortCol", 0), 0, 2, 0
)
sortOrder = self.optState.validIntTuple(
self.optState.getInt("GuiSession", "sortOrder", Qt.DescendingOrder),
self.optState.getInt("GuiSessionLog", "sortOrder", Qt.DescendingOrder),
sortValid, Qt.DescendingOrder
)
self.listBox.sortByColumn(sortCol, sortOrder)
self.listBox.setSortingEnabled(True)
# Word Bar
self.barHeight = int(round(0.5*self.theTheme.fontPixelSize))
self.barWidth = self.mainConf.pxInt(200)
self.barImage = QPixmap(self.barHeight, self.barHeight)
self.barImage.fill(self.palette().highlight().color())
# Session Info
self.infoBox = QGroupBox("Sum Total Time", self)
self.infoBoxForm = QGridLayout(self)
self.infoBox.setLayout(self.infoBoxForm)
self.infoBox = QGroupBox("Sum Totals", self)
self.infoForm = QGridLayout(self)
self.infoBox.setLayout(self.infoForm)
self.labelTotal = QLabel(self._formatTime(0))
self.labelTotal.setFont(self.monoFont)
@@ -107,105 +134,296 @@ class GuiSessionLogView(QDialog):
self.labelFilter.setFont(self.monoFont)
self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.infoBoxForm.addWidget(QLabel("All:"), 0, 0)
self.infoBoxForm.addWidget(self.labelTotal, 0, 1)
self.infoBoxForm.addWidget(QLabel("Filtered:"), 1, 0)
self.infoBoxForm.addWidget(self.labelFilter, 1, 1)
self.novelWords = QLabel("0")
self.novelWords.setFont(self.monoFont)
self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.notesWords = QLabel("0")
self.notesWords.setFont(self.monoFont)
self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.totalWords = QLabel("0")
self.totalWords.setFont(self.monoFont)
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.infoForm.addWidget(QLabel("Total Time:"), 0, 0)
self.infoForm.addWidget(QLabel("Filtered Time:"), 1, 0)
self.infoForm.addWidget(QLabel("Novel Word Count:"), 2, 0)
self.infoForm.addWidget(QLabel("Notes Word Count:"), 3, 0)
self.infoForm.addWidget(QLabel("Total Word Count:"), 4, 0)
self.infoForm.addWidget(self.labelTotal, 0, 1)
self.infoForm.addWidget(self.labelFilter, 1, 1)
self.infoForm.addWidget(self.novelWords, 2, 1)
self.infoForm.addWidget(self.notesWords, 3, 1)
self.infoForm.addWidget(self.totalWords, 4, 1)
self.infoForm.setRowStretch(5, 1)
# Filter Options
self.filterBox = QGroupBox("Filters", self)
self.filterBoxForm = QGridLayout(self)
self.filterBox.setLayout(self.filterBoxForm)
sPx = self.theTheme.baseIconSize
self.hideZeros = QCheckBox("Hide zero word count", self)
self.filterBox = QGroupBox("Filters", self)
self.filterForm = QGridLayout(self)
self.filterBox.setLayout(self.filterForm)
self.incNovel = QSwitch(width=2*sPx, height=sPx)
self.incNovel.setChecked(
self.optState.getBool("GuiSessionLog", "incNovel", True)
)
self.incNovel.clicked.connect(self._updateListBox)
self.incNotes = QSwitch(width=2*sPx, height=sPx)
self.incNotes.setChecked(
self.optState.getBool("GuiSessionLog", "incNotes", True)
)
self.incNotes.clicked.connect(self._updateListBox)
self.hideZeros = QSwitch(width=2*sPx, height=sPx)
self.hideZeros.setChecked(
self.optState.getBool("GuiSession", "hideZeros", True)
self.optState.getBool("GuiSessionLog", "hideZeros", True)
)
self.hideZeros.stateChanged.connect(self._doHideZeros)
self.hideZeros.clicked.connect(self._updateListBox)
self.hideNegative = QCheckBox("Hide negative word count", self)
self.hideNegative = QSwitch(width=2*sPx, height=sPx)
self.hideNegative.setChecked(
self.optState.getBool("GuiSession", "hideNegative", False)
self.optState.getBool("GuiSessionLog", "hideNegative", False)
)
self.hideNegative.stateChanged.connect(self._doHideNegative)
self.hideNegative.clicked.connect(self._updateListBox)
self.filterBoxForm.addWidget(self.hideZeros, 0, 0)
self.filterBoxForm.addWidget(self.hideNegative, 1, 0)
self.groupByDay = QSwitch(width=2*sPx, height=sPx)
self.groupByDay.setChecked(
self.optState.getBool("GuiSessionLog", "groupByDay", False)
)
self.groupByDay.clicked.connect(self._updateListBox)
self.filterForm.addWidget(QLabel("Count novel files"), 0, 0)
self.filterForm.addWidget(QLabel("Count note files"), 1, 0)
self.filterForm.addWidget(QLabel("Hide zero word count"), 2, 0)
self.filterForm.addWidget(QLabel("Hide negative word count"), 3, 0)
self.filterForm.addWidget(QLabel("Group entries by day"), 4, 0)
self.filterForm.addWidget(self.incNovel, 0, 1)
self.filterForm.addWidget(self.incNotes, 1, 1)
self.filterForm.addWidget(self.hideZeros, 2, 1)
self.filterForm.addWidget(self.hideNegative, 3, 1)
self.filterForm.addWidget(self.groupByDay, 4, 1)
self.filterForm.setRowStretch(5, 1)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.rejected.connect(self._doClose)
self.btnSave = self.buttonBox.addButton("Save As", QDialogButtonBox.ActionRole)
self.saveMenu = QMenu(self)
self.btnSave.setMenu(self.saveMenu)
self.saveJSON = QAction("JSON Data File (.json)", self)
self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON))
self.saveMenu.addAction(self.saveJSON)
self.saveCSV = QAction("CSV Data File (.csv)", self)
self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV))
self.saveMenu.addAction(self.saveCSV)
# Assemble
self.outerBox = QGridLayout()
self.outerBox.addWidget(self.listBox, 0, 0, 1, 2)
self.outerBox.addWidget(self.infoBox, 1, 0)
self.outerBox.addWidget(self.filterBox, 1, 1)
self.outerBox.addWidget(self.buttonBox, 2, 0, 1, 2)
self.outerBox.setRowStretch(0, 1)
self.setLayout(self.outerBox)
self.show()
logger.debug("GuiSessionLog initialisation complete")
logger.debug("SessionLogView initialisation complete")
self._loadSessionLog()
qApp.processEvents()
self._loadLogFile()
self._updateListBox()
return
def _loadSessionLog(self):
##
# Slots
##
logFile = path.join(self.theProject.projMeta, nwFiles.SESS_INFO)
if not path.isfile(logFile):
logger.warning("No session log file found for this project.")
return False
self.listBox.clear()
self.timeFilter = 0.0
self.timeTotal = 0.0
def _doClose(self):
"""Save the state of the window, clear cache, end close.
"""
self.logData = []
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0))
widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1))
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
sortCol = self.listBox.sortColumn()
sortOrder = self.listBox.header().sortIndicatorOrder()
incNovel = self.incNovel.isChecked()
incNotes = self.incNotes.isChecked()
hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked()
self.optState.setValue("GuiSessionLog", "winWidth", winWidth)
self.optState.setValue("GuiSessionLog", "winHeight", winHeight)
self.optState.setValue("GuiSessionLog", "widthCol0", widthCol0)
self.optState.setValue("GuiSessionLog", "widthCol1", widthCol1)
self.optState.setValue("GuiSessionLog", "widthCol2", widthCol2)
self.optState.setValue("GuiSessionLog", "sortCol", sortCol)
self.optState.setValue("GuiSessionLog", "sortOrder", sortOrder)
self.optState.setValue("GuiSessionLog", "incNovel", incNovel)
self.optState.setValue("GuiSessionLog", "incNotes", incNotes)
self.optState.setValue("GuiSessionLog", "hideZeros", hideZeros)
self.optState.setValue("GuiSessionLog", "hideNegative", hideNegative)
self.optState.setValue("GuiSessionLog", "groupByDay", groupByDay)
self.optState.saveSettings()
self.close()
return
def _saveData(self, dataFmt):
"""Save the content of the list box to a file.
"""
fileExt = ""
textFmt = ""
if dataFmt == self.FMT_JSON:
fileExt = "json"
textFmt = "JSON Data File"
elif dataFmt == self.FMT_CSV:
fileExt = "csv"
textFmt = "CSV Data File"
else:
return False
# Generate the file name
if fileExt:
fileName = "sessionStats.%s" % fileExt
saveDir = self.mainConf.lastPath
savePath = path.join(saveDir, fileName)
if not path.isdir(saveDir):
saveDir = self.mainConf.homePath
if self.mainConf.showGUI:
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName(
self, "Save Document As", savePath, options=dlgOpt
)
if saveTo[0]:
savePath = saveTo[0]
else:
return False
self.mainConf.setLastPath(savePath)
else:
return False
# Do the actual writing
wSuccess = False
errMsg = ""
logger.debug("Loading session log file")
try:
with open(logFile,mode="r",encoding="utf8") as inFile:
with open(savePath, mode="w", encoding="utf8") as outFile:
if dataFmt == self.FMT_JSON:
jsonData = []
for _, sD, tT, wD, wA, wB in self.filterData:
jsonData.append({
"date": sD,
"length": tT,
"newWords": wD,
"novelWords": wA,
"noteWords": wB,
})
outFile.write(json.dumps(jsonData, indent=2))
wSuccess = True
elif dataFmt == self.FMT_CSV:
outFile.write(
"\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n" % (
"Date", "Length (sec)", "Words Changed", "Novel Words", "Note Words"
)
)
for _, sD, tT, wD, wA, wB in self.filterData:
outFile.write(
"\"%s\",%d,%d,%d,%d\n" % (sD, tT, wD, wA, wB)
)
wSuccess = True
else:
errMsg = "Unknown format"
except Exception as e:
errMsg = str(e)
# Report to user
if self.mainConf.showGUI:
if wSuccess:
self.theParent.makeAlert(
"%s file successfully written to:<br> %s" % (
textFmt, savePath
), nwAlert.INFO
)
else:
self.theParent.makeAlert(
"Failed to write %s file. %s" % (
textFmt, errMsg
), nwAlert.ERROR
)
return True
##
# Internal Functions
##
def _loadLogFile(self):
"""Load the content of the log file into a buffer.
"""
logger.debug("Loading session log file")
self.logData = []
ttNovel = 0
ttNotes = 0
ttTime = 0
try:
logFile = path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
with open(logFile, mode="r", encoding="utf8") as inFile:
for inLine in inFile:
inData = inLine.split()
if len(inData) != 8:
if inLine.startswith("#"):
if inLine.startswith("# Offset"):
self.wordOffset = int(inLine[9:].strip())
logger.verbose(
"Initial word count when log was started is %d" % self.wordOffset
)
continue
inData = inLine.split()
if len(inData) != 6:
continue
dStart = datetime.strptime(
"%s %s" % (inData[1],inData[2]), nwConst.tStampFmt
"%s %s" % (inData[0], inData[1]), nwConst.tStampFmt
)
dEnd = datetime.strptime(
"%s %s" % (inData[4],inData[5]), nwConst.tStampFmt
"%s %s" % (inData[2], inData[3]), nwConst.tStampFmt
)
nWords = int(inData[7])
tDiff = dEnd - dStart
sDiff = tDiff.total_seconds()
ttTime += sDiff
self.timeTotal += sDiff
if abs(nWords) > 0:
self.timeFilter += sDiff
wcNovel = int(inData[4])
wcNotes = int(inData[5])
ttNovel = wcNovel
ttNotes = wcNotes
if hideZeros and nWords == 0:
continue
if hideNegative and nWords < 0:
continue
newItem = QTreeWidgetItem(
[str(dStart), self._formatTime(sDiff), str(nWords), ""]
)
newItem.setTextAlignment(1,Qt.AlignRight)
newItem.setTextAlignment(2,Qt.AlignRight)
newItem.setFont(0,self.monoFont)
newItem.setFont(1,self.monoFont)
newItem.setFont(2,self.monoFont)
self.listBox.addTopLevelItem(newItem)
self.logData.append((dStart, sDiff, wcNovel, wcNotes))
except Exception as e:
self.theParent.makeAlert(
@@ -213,47 +431,126 @@ class GuiSessionLogView(QDialog):
)
return False
self.labelFilter.setText(self._formatTime(self.timeFilter))
self.labelTotal.setText(self._formatTime(self.timeTotal))
self.labelTotal.setText(self._formatTime(ttTime))
self.novelWords.setText("{:n}".format(ttNovel))
self.notesWords.setText("{:n}".format(ttNotes))
self.totalWords.setText("{:n}".format(ttNovel + ttNotes))
return True
def _doClose(self):
def _updateListBox(self):
"""Load/reload the content of the list box.
"""
self.listBox.clear()
self.timeFilter = 0.0
widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0))
widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1))
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
sortCol = self.listBox.sortColumn()
sortOrder = self.listBox.header().sortIndicatorOrder()
incNovel = self.incNovel.isChecked()
incNotes = self.incNotes.isChecked()
hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked()
self.optState.setValue("GuiSession", "widthCol0", widthCol0)
self.optState.setValue("GuiSession", "widthCol1", widthCol1)
self.optState.setValue("GuiSession", "widthCol2", widthCol2)
self.optState.setValue("GuiSession", "sortCol", sortCol)
self.optState.setValue("GuiSession", "sortOrder", sortOrder)
self.optState.setValue("GuiSession", "hideZeros", hideZeros)
self.optState.setValue("GuiSession", "hideNegative", hideNegative)
# Group the data
if groupByDay:
tempData = []
sessDate = None
sessTime = 0
lstNovel = 0
lstNotes = 0
self.optState.saveSettings()
self.close()
for n, (dStart, sDiff, wcNovel, wcNotes) in enumerate(self.logData):
if n == 0:
sessDate = dStart.date()
if sessDate != dStart.date():
tempData.append((sessDate, sessTime, lstNovel, lstNotes))
sessDate = dStart.date()
sessTime = sDiff
lstNovel = wcNovel
lstNotes = wcNotes
else:
sessTime += sDiff
lstNovel = wcNovel
lstNotes = wcNotes
return
if sessDate is not None:
tempData.append((sessDate, sessTime, lstNovel, lstNotes))
def _doHideZeros(self, newState):
self._loadSessionLog()
return
else:
tempData = self.logData
def _doHideNegative(self, newState):
self._loadSessionLog()
return
# Calculate Word Diff
self.filterData = []
pcTotal = 0
listMax = 0
isFirst = True
for dStart, sDiff, wcNovel, wcNotes in tempData:
wcTotal = 0
if incNovel:
wcTotal += wcNovel
if incNotes:
wcTotal += wcNotes
dwTotal = wcTotal - pcTotal
if hideZeros and dwTotal == 0:
continue
if hideNegative and dwTotal < 0:
continue
if isFirst:
# Subtract the offset from the first list entry
dwTotal -= self.wordOffset
dwTotal = max(dwTotal, 1) # Don't go zero or negative
isFirst = False
if groupByDay:
sStart = dStart.strftime(nwConst.dStampFmt)
else:
sStart = dStart.strftime(nwConst.tStampFmt)
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
listMax = max(listMax, dwTotal)
pcTotal = wcTotal
# Populate the list
for _, sStart, sDiff, nWords, _, _ in self.filterData:
newItem = QTreeWidgetItem()
newItem.setText(self.C_TIME, sStart)
newItem.setText(self.C_LENGTH, self._formatTime(sDiff))
newItem.setText(self.C_COUNT, "{:n}".format(nWords))
if nWords > 0 and listMax > 0:
theBar = self.barImage.scaled(
int(200*nWords/listMax),
self.barHeight,
Qt.IgnoreAspectRatio,
Qt.FastTransformation
)
newItem.setData(self.C_BAR, Qt.DecorationRole, theBar)
newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setFont(self.C_TIME, self.monoFont)
newItem.setFont(self.C_LENGTH, self.monoFont)
newItem.setFont(self.C_COUNT, self.monoFont)
self.listBox.addTopLevelItem(newItem)
self.timeFilter += sDiff
self.labelFilter.setText(self._formatTime(self.timeFilter))
return True
def _formatTime(self, tS):
"""Format the time spent in 00:00:00 format.
"""
tM = int(tS/60)
tH = int(tM/60)
tM = tM - tH*60
tS = tS - tM*60 - tH*3600
return "%02d:%02d:%02d" % (tH,tM,tS)
# END Class GuiSessionLogView
# END Class GuiSessionLog
+2
View File
@@ -139,6 +139,8 @@ class GuiTheme:
logger.verbose("GUI Scale: %.2f" % self.guiScale)
self.guiFont = qApp.font()
self.guiFontFixed = QFontDatabase.systemFont(QFontDatabase.FixedFont)
qMetric = QFontMetrics(self.guiFont)
self.fontPointSize = self.guiFont.pointSizeF()
self.fontPixelSize = int(round(qMetric.height()))