Merge branch 'master' into editor_footer
This commit is contained in:
+2
-2
@@ -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
@@ -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(" ", " "*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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
|
||||
Reference in New Issue
Block a user