Cleaned up the source files in the gui folder a bit.

This commit is contained in:
Veronica K. B. Olsen
2019-10-31 13:56:00 +01:00
parent 9fea45b7df
commit 6f893bf321
16 changed files with 45 additions and 41 deletions
+600
View File
@@ -0,0 +1,600 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Config Editor
novelWriter GUI Config Editor
=================================
Class holding the config dialog
File History:
Created: 2019-06-10 [0.1.5]
"""
import logging
import nw
from os import path
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel, QFont
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel,
QWidget, QTabWidget, QDialogButtonBox, QSpinBox, QGroupBox, QComboBox, QMessageBox,
QCheckBox, QGridLayout, QFontComboBox, QPushButton, QFileDialog
)
from nw.enum import nwAlert
from nw.constants import nwQuotes
logger = logging.getLogger(__name__)
class GuiConfigEditor(QDialog):
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising ConfigEditor ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
self.setWindowTitle("Preferences")
self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"gear.svg"))
self.svgGradient = QSvgWidget(path.join(self.gradPath))
self.svgGradient.setFixedSize(QSize(64,64))
self.theProject.countStatus()
self.tabMain = GuiConfigEditGeneral(self.theParent)
self.tabEditor = GuiConfigEditEditor(self.theParent)
self.tabWidget = QTabWidget()
self.tabWidget.addTab(self.tabMain, "General")
self.tabWidget.addTab(self.tabEditor, "Editor")
self.setLayout(self.outerBox)
self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
self.innerBox.addWidget(self.tabWidget)
self.innerBox.addWidget(self.buttonBox)
self.show()
logger.debug("ConfigEditor initialisation complete")
return
##
# Buttons
##
def _doSave(self):
logger.verbose("ConfigEditor save button clicked")
validEntries = True
needsRestart = False
retA, retB = self.tabMain.saveValues()
validEntries &= retA
needsRestart |= retB
retA, retB = self.tabEditor.saveValues()
validEntries &= retA
needsRestart |= retB
if needsRestart:
msgBox = QMessageBox()
msgBox.information(
self, "Preferences",
"Some changes will not be applied until<br>%s has been restarted." % nw.__package__
)
if validEntries:
self.accept()
return
def _doClose(self):
logger.verbose("ConfigEditor close button clicked")
self.close()
return
# END Class GuiConfigEditor
class GuiConfigEditGeneral(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.outerBox = QGridLayout()
# User Interface
self.guiLook = QGroupBox("User Interface", self)
self.guiLookForm = QGridLayout(self)
self.guiLook.setLayout(self.guiLookForm)
self.guiLookTheme = QComboBox()
self.guiLookTheme.setMinimumWidth(200)
self.theThemes = self.theTheme.listThemes()
for themeDir, themeName in self.theThemes:
self.guiLookTheme.addItem(themeName, themeDir)
themeIdx = self.guiLookTheme.findData(self.mainConf.guiTheme)
if themeIdx != -1:
self.guiLookTheme.setCurrentIndex(themeIdx)
self.guiLookSyntax = QComboBox()
self.guiLookSyntax.setMinimumWidth(200)
self.theSyntaxes = self.theTheme.listSyntax()
for syntaxFile, syntaxName in self.theSyntaxes:
self.guiLookSyntax.addItem(syntaxName, syntaxFile)
syntaxIdx = self.guiLookSyntax.findData(self.mainConf.guiSyntax)
if syntaxIdx != -1:
self.guiLookSyntax.setCurrentIndex(syntaxIdx)
self.guiLookForm.addWidget(QLabel("Theme"), 0, 0)
self.guiLookForm.addWidget(self.guiLookTheme, 0, 1)
self.guiLookForm.addWidget(QLabel("Syntax"), 1, 0)
self.guiLookForm.addWidget(self.guiLookSyntax, 1, 1)
self.guiLookForm.setColumnStretch(2, 1)
# Spell Checking
self.spellLang = QGroupBox("Spell Checker", self)
self.spellLangForm = QGridLayout(self)
self.spellLang.setLayout(self.spellLangForm)
self.spellLangList = QComboBox(self)
for spTag, spName in self.theParent.docEditor.theDict.listDictionaries():
self.spellLangList.addItem(spName, spTag)
spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
if spellIdx != -1:
self.spellLangList.setCurrentIndex(spellIdx)
self.spellLangForm.addWidget(QLabel("Language"), 0, 0)
self.spellLangForm.addWidget(self.spellLangList, 0, 1)
self.spellLangForm.setColumnStretch(2, 1)
# AutoSave
self.autoSave = QGroupBox("Automatic Save", self)
self.autoSaveForm = QGridLayout(self)
self.autoSave.setLayout(self.autoSaveForm)
self.autoSaveDoc = QSpinBox(self)
self.autoSaveDoc.setMinimum(5)
self.autoSaveDoc.setMaximum(600)
self.autoSaveDoc.setSingleStep(1)
self.autoSaveDoc.setValue(self.mainConf.autoSaveDoc)
self.autoSaveProj = QSpinBox(self)
self.autoSaveProj.setMinimum(5)
self.autoSaveProj.setMaximum(600)
self.autoSaveProj.setSingleStep(1)
self.autoSaveProj.setValue(self.mainConf.autoSaveProj)
self.autoSaveForm.addWidget(QLabel("Document"), 0, 0)
self.autoSaveForm.addWidget(self.autoSaveDoc, 0, 1)
self.autoSaveForm.addWidget(QLabel("seconds"), 0, 2)
self.autoSaveForm.addWidget(QLabel("Project"), 1, 0)
self.autoSaveForm.addWidget(self.autoSaveProj, 1, 1)
self.autoSaveForm.addWidget(QLabel("seconds"), 1, 2)
self.autoSaveForm.setColumnStretch(3, 1)
# Backup
self.projBackup = QGroupBox("Backup Folder", self)
self.projBackupForm = QGridLayout(self)
self.projBackup.setLayout(self.projBackupForm)
self.projBackupPath = QLineEdit()
if path.isdir(self.mainConf.backupPath):
self.projBackupPath.setText(self.mainConf.backupPath)
self.projBackupGetPath = QPushButton(self.theTheme.getIcon("folder"),"")
self.projBackupGetPath.clicked.connect(self._backupFolder)
self.projBackupClose = QCheckBox("Run on close",self)
self.projBackupClose.setToolTip("Backup automatically on project close.")
if self.mainConf.backupOnClose:
self.projBackupClose.setCheckState(Qt.Checked)
else:
self.projBackupClose.setCheckState(Qt.Unchecked)
self.projBackupAsk = QCheckBox("Ask before backup",self)
self.projBackupAsk.setToolTip("Ask before backup.")
if self.mainConf.askBeforeBackup:
self.projBackupAsk.setCheckState(Qt.Checked)
else:
self.projBackupAsk.setCheckState(Qt.Unchecked)
self.projBackupForm.addWidget(self.projBackupPath, 0, 0, 1, 2)
self.projBackupForm.addWidget(self.projBackupGetPath, 0, 2)
self.projBackupForm.addWidget(self.projBackupClose, 1, 0)
self.projBackupForm.addWidget(self.projBackupAsk, 1, 1, 1, 2)
self.projBackupForm.setColumnStretch(1, 1)
# Assemble
self.outerBox.addWidget(self.guiLook, 0, 0)
self.outerBox.addWidget(self.spellLang, 1, 0)
self.outerBox.addWidget(self.autoSave, 2, 0)
self.outerBox.addWidget(self.projBackup, 3, 0)
self.outerBox.setColumnStretch(2, 1)
self.outerBox.setRowStretch(4, 1)
self.setLayout(self.outerBox)
return
def saveValues(self):
validEntries = True
needsRestart = False
guiTheme = self.guiLookTheme.currentData()
guiSyntax = self.guiLookSyntax.currentData()
spellLanguage = self.spellLangList.currentData()
autoSaveDoc = self.autoSaveDoc.value()
autoSaveProj = self.autoSaveProj.value()
backupPath = self.projBackupPath.text()
backupOnClose = self.projBackupClose.isChecked()
askBeforeBackup = self.projBackupAsk.isChecked()
# Check if restart is needed
needsRestart |= self.mainConf.guiTheme != guiTheme
self.mainConf.guiTheme = guiTheme
self.mainConf.guiSyntax = guiSyntax
self.mainConf.spellLanguage = spellLanguage
self.mainConf.autoSaveDoc = autoSaveDoc
self.mainConf.autoSaveProj = autoSaveProj
self.mainConf.backupPath = backupPath
self.mainConf.backupOnClose = backupOnClose
self.mainConf.askBeforeBackup = askBeforeBackup
self.mainConf.confChanged = True
return validEntries, needsRestart
##
# Internal Functions
##
def _backupFolder(self):
currDir = self.projBackupPath.text()
if not path.isdir(currDir):
currDir = ""
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog
newDir = QFileDialog.getExistingDirectory(
self,"Backup Directory",currDir,options=dlgOpt
)
if newDir:
self.projBackupPath.setText(newDir)
return True
return False
# END Class GuiConfigEditGeneral
class GuiConfigEditEditor(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
self.mainConf = nw.CONFIG
self.theParent = theParent
self.outerBox = QGridLayout()
# Text Style
self.textStyle = QGroupBox("Text Style", self)
self.textStyleForm = QGridLayout(self)
self.textStyle.setLayout(self.textStyleForm)
self.textStyleFont = QFontComboBox()
self.textStyleFont.setMaximumWidth(250)
self.textStyleFont.setCurrentFont(QFont(self.mainConf.textFont))
self.textStyleSize = QSpinBox(self)
self.textStyleSize.setMinimum(5)
self.textStyleSize.setMaximum(120)
self.textStyleSize.setSingleStep(1)
self.textStyleSize.setValue(self.mainConf.textSize)
self.textStyleForm.addWidget(QLabel("Font family"), 0, 0)
self.textStyleForm.addWidget(self.textStyleFont, 0, 1)
self.textStyleForm.addWidget(QLabel("Size"), 0, 2)
self.textStyleForm.addWidget(self.textStyleSize, 0, 3)
self.textStyleForm.setColumnStretch(4, 1)
# Text Flow
self.textFlow = QGroupBox("Text Flow", self)
self.textFlowForm = QGridLayout(self)
self.textFlow.setLayout(self.textFlowForm)
self.textFlowFixed = QCheckBox("Fixed width",self)
self.textFlowFixed.setToolTip("Make text in editor fixed width and scale margins instead.")
if self.mainConf.textFixedW:
self.textFlowFixed.setCheckState(Qt.Checked)
else:
self.textFlowFixed.setCheckState(Qt.Unchecked)
self.textFlowWidth = QSpinBox(self)
self.textFlowWidth.setMinimum(300)
self.textFlowWidth.setMaximum(10000)
self.textFlowWidth.setSingleStep(10)
self.textFlowWidth.setValue(self.mainConf.textWidth)
self.textFlowJustify = QCheckBox("Justify text",self)
self.textFlowJustify.setToolTip("Justify text in main document editor.")
if self.mainConf.doJustify:
self.textFlowJustify.setCheckState(Qt.Checked)
else:
self.textFlowJustify.setCheckState(Qt.Unchecked)
self.textFlowForm.addWidget(self.textFlowFixed, 0, 0)
self.textFlowForm.addWidget(self.textFlowWidth, 0, 1)
self.textFlowForm.addWidget(QLabel("px"), 0, 2)
self.textFlowForm.addWidget(self.textFlowJustify, 1, 0)
self.textFlowForm.setColumnStretch(4, 1)
# Text Margins
self.textMargin = QGroupBox("Margins", self)
self.textMarginForm = QGridLayout(self)
self.textMargin.setLayout(self.textMarginForm)
self.textMarginDoc = QSpinBox(self)
self.textMarginDoc.setMinimum(0)
self.textMarginDoc.setMaximum(2000)
self.textMarginDoc.setSingleStep(1)
self.textMarginDoc.setValue(self.mainConf.textMargin)
self.textMarginTab = QSpinBox(self)
self.textMarginTab.setMinimum(0)
self.textMarginTab.setMaximum(200)
self.textMarginTab.setSingleStep(1)
self.textMarginTab.setValue(self.mainConf.tabWidth)
self.textMarginTab.setToolTip("Requires Qt 5.9 or later.")
self.textMarginForm.addWidget(QLabel("Document"), 0, 0)
self.textMarginForm.addWidget(self.textMarginDoc, 0, 1)
self.textMarginForm.addWidget(QLabel("px"), 0, 2)
self.textMarginForm.addWidget(QLabel("Tab width"), 2, 0)
self.textMarginForm.addWidget(self.textMarginTab, 2, 1)
self.textMarginForm.addWidget(QLabel("px"), 2, 2)
self.textMarginForm.setColumnStretch(4, 1)
# Automatic Features
self.autoReplace = QGroupBox("Automatic Features", self)
self.autoReplaceForm = QGridLayout(self)
self.autoReplace.setLayout(self.autoReplaceForm)
self.autoSelect = QCheckBox(self)
self.autoSelect.setToolTip("Auto-select word under cursor when applying formatting.")
if self.mainConf.autoSelect:
self.autoSelect.setCheckState(Qt.Checked)
else:
self.autoSelect.setCheckState(Qt.Unchecked)
self.autoReplaceMain = QCheckBox(self)
self.autoReplaceMain.setToolTip("Auto-replace text as you type.")
if self.mainConf.doReplace:
self.autoReplaceMain.setCheckState(Qt.Checked)
else:
self.autoReplaceMain.setCheckState(Qt.Unchecked)
self.autoReplaceSQ = QCheckBox(self)
self.autoReplaceSQ.setToolTip("Auto-replace single quotes.")
if self.mainConf.doReplaceSQuote:
self.autoReplaceSQ.setCheckState(Qt.Checked)
else:
self.autoReplaceSQ.setCheckState(Qt.Unchecked)
self.autoReplaceDQ = QCheckBox(self)
self.autoReplaceDQ.setToolTip("Auto-replace double quotes.")
if self.mainConf.doReplaceDQuote:
self.autoReplaceDQ.setCheckState(Qt.Checked)
else:
self.autoReplaceDQ.setCheckState(Qt.Unchecked)
self.autoReplaceDash = QCheckBox(self)
self.autoReplaceDash.setToolTip("Auto-replace double and triple hyphens with short and long dash.")
if self.mainConf.doReplaceDash:
self.autoReplaceDash.setCheckState(Qt.Checked)
else:
self.autoReplaceDash.setCheckState(Qt.Unchecked)
self.autoReplaceDots = QCheckBox(self)
self.autoReplaceDots.setToolTip("Auto-replace three dots with ellipsis.")
if self.mainConf.doReplaceDots:
self.autoReplaceDots.setCheckState(Qt.Checked)
else:
self.autoReplaceDots.setCheckState(Qt.Unchecked)
self.autoReplaceForm.addWidget(QLabel("Auto-select text"), 0, 0)
self.autoReplaceForm.addWidget(self.autoSelect, 0, 1)
self.autoReplaceForm.addWidget(QLabel("Auto-replace:"), 1, 0)
self.autoReplaceForm.addWidget(self.autoReplaceMain, 1, 1)
self.autoReplaceForm.addWidget(QLabel("\u2192 Single quotes"), 2, 0)
self.autoReplaceForm.addWidget(self.autoReplaceSQ, 2, 1)
self.autoReplaceForm.addWidget(QLabel("\u2192 Double quotes"), 3, 0)
self.autoReplaceForm.addWidget(self.autoReplaceDQ, 3, 1)
self.autoReplaceForm.addWidget(QLabel("\u2192 Hyphens with dash"), 4, 0)
self.autoReplaceForm.addWidget(self.autoReplaceDash, 4, 1)
self.autoReplaceForm.addWidget(QLabel("\u2192 Dots with ellipsis"), 5, 0)
self.autoReplaceForm.addWidget(self.autoReplaceDots, 5, 1)
self.autoReplaceForm.setColumnStretch(2, 1)
self.autoReplaceForm.setRowStretch(6, 1)
# Quote Style
self.quoteStyle = QGroupBox("Quotation Style", self)
self.quoteStyleForm = QGridLayout(self)
self.quoteStyle.setLayout(self.quoteStyleForm)
self.quoteSingleStyleO = QLineEdit()
self.quoteSingleStyleO.setMaxLength(1)
self.quoteSingleStyleO.setFixedWidth(30)
self.quoteSingleStyleO.setAlignment(Qt.AlignCenter)
self.quoteSingleStyleO.setText(self.mainConf.fmtSingleQuotes[0])
self.quoteSingleStyleC = QLineEdit()
self.quoteSingleStyleC.setMaxLength(1)
self.quoteSingleStyleC.setFixedWidth(30)
self.quoteSingleStyleC.setAlignment(Qt.AlignCenter)
self.quoteSingleStyleC.setText(self.mainConf.fmtSingleQuotes[1])
self.quoteDoubleStyleO = QLineEdit()
self.quoteDoubleStyleO.setMaxLength(1)
self.quoteDoubleStyleO.setFixedWidth(30)
self.quoteDoubleStyleO.setAlignment(Qt.AlignCenter)
self.quoteDoubleStyleO.setText(self.mainConf.fmtDoubleQuotes[0])
self.quoteDoubleStyleC = QLineEdit()
self.quoteDoubleStyleC.setMaxLength(1)
self.quoteDoubleStyleC.setFixedWidth(30)
self.quoteDoubleStyleC.setAlignment(Qt.AlignCenter)
self.quoteDoubleStyleC.setText(self.mainConf.fmtDoubleQuotes[1])
self.quoteStyleForm.addWidget(QLabel("Single Quotes"), 0, 0, 1, 3)
self.quoteStyleForm.addWidget(QLabel("Open"), 1, 0)
self.quoteStyleForm.addWidget(self.quoteSingleStyleO, 1, 1)
self.quoteStyleForm.addWidget(QLabel("Close"), 1, 2)
self.quoteStyleForm.addWidget(self.quoteSingleStyleC, 1, 3)
self.quoteStyleForm.addWidget(QLabel("Double Quotes"), 2, 0, 1, 3)
self.quoteStyleForm.addWidget(QLabel("Open"), 3, 0)
self.quoteStyleForm.addWidget(self.quoteDoubleStyleO, 3, 1)
self.quoteStyleForm.addWidget(QLabel("Close"), 3, 2)
self.quoteStyleForm.addWidget(self.quoteDoubleStyleC, 3, 3)
self.quoteStyleForm.setColumnStretch(4, 1)
self.quoteStyleForm.setRowStretch(4, 1)
# Writing Guides
self.showGuides = QGroupBox("Writing Guides", self)
self.showGuidesForm = QGridLayout(self)
self.showGuides.setLayout(self.showGuidesForm)
self.showTabsNSpaces = QCheckBox("Show tabs and spaces",self)
if self.mainConf.showTabsNSpaces:
self.showTabsNSpaces.setCheckState(Qt.Checked)
else:
self.showTabsNSpaces.setCheckState(Qt.Unchecked)
self.showLineEndings = QCheckBox("Show line endings",self)
if self.mainConf.showTabsNSpaces:
self.showLineEndings.setCheckState(Qt.Checked)
else:
self.showLineEndings.setCheckState(Qt.Unchecked)
self.showGuidesForm.addWidget(self.showTabsNSpaces, 0, 0)
self.showGuidesForm.addWidget(self.showLineEndings, 1, 0)
# Assemble
self.outerBox.addWidget(self.textStyle, 0, 0, 1, 2)
self.outerBox.addWidget(self.textFlow, 1, 0)
self.outerBox.addWidget(self.textMargin, 1, 1)
self.outerBox.addWidget(self.quoteStyle, 2, 0)
self.outerBox.addWidget(self.autoReplace, 2, 1, 2, 1)
self.outerBox.addWidget(self.showGuides, 3, 0)
self.outerBox.setColumnStretch(2, 1)
self.setLayout(self.outerBox)
return
def saveValues(self):
validEntries = True
textFont = self.textStyleFont.currentFont().family()
textSize = self.textStyleSize.value()
self.mainConf.textFont = textFont
self.mainConf.textSize = textSize
textWidth = self.textFlowWidth.value()
textFixedW = self.textFlowFixed.isChecked()
doJustify = self.textFlowJustify.isChecked()
self.mainConf.textWidth = textWidth
self.mainConf.textFixedW = textFixedW
self.mainConf.doJustify = doJustify
textMargin = self.textMarginDoc.value()
tabWidth = self.textMarginTab.value()
self.mainConf.textMargin = textMargin
self.mainConf.tabWidth = tabWidth
autoSelect = self.autoSelect.isChecked()
doReplace = self.autoReplaceMain.isChecked()
doReplaceSQuote = self.autoReplaceSQ.isChecked()
doReplaceDQuote = self.autoReplaceDQ.isChecked()
doReplaceDash = self.autoReplaceDash.isChecked()
doReplaceDots = self.autoReplaceDash.isChecked()
self.mainConf.autoSelect = autoSelect
self.mainConf.doReplace = doReplace
self.mainConf.doReplaceSQuote = doReplaceSQuote
self.mainConf.doReplaceDQuote = doReplaceDQuote
self.mainConf.doReplaceDash = doReplaceDash
self.mainConf.doReplaceDots = doReplaceDots
fmtSingleQuotesO = self.quoteSingleStyleO.text()
fmtSingleQuotesC = self.quoteSingleStyleC.text()
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
showTabsNSpaces = self.showTabsNSpaces.isChecked()
showLineEndings = self.showLineEndings.isChecked()
self.mainConf.showTabsNSpaces = showTabsNSpaces
self.mainConf.showLineEndings = showLineEndings
self.mainConf.confChanged = True
return validEntries, False
##
# Internal Functions
##
def _checkQuoteSymbol(self, toCheck):
if len(toCheck) != 1:
return False
if toCheck in nwQuotes.SYMBOLS:
return True
return False
# END Class GuiConfigEditEditor
+708
View File
@@ -0,0 +1,708 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Export Tools
novelWriter GUI Export Tools
================================
Tool for exporting project files to other formats
File History:
Created: 2019-10-13 [0.2.3]
"""
import logging
import time
import nw
from os import path
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout, QGroupBox, QCheckBox,
QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar, QSpinBox, QMessageBox
)
from nw.project.document import NWDoc
from nw.tools.translate import numberToWord
from nw.tools.optlaststate import OptLastState
from nw.convert.file.text import TextFile
from nw.convert.file.html import HtmlFile
from nw.convert.file.markdown import MarkdownFile
from nw.convert.file.latex import LaTeXFile
from nw.convert.file.concat import ConcatFile
from nw.common import packageRefURL
from nw.constants import nwFiles
from nw.enum import nwItemType, nwAlert
logger = logging.getLogger(__name__)
class GuiExport(QDialog):
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiExport ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.optState = ExportLastState(self.theProject,nwFiles.EXPORT_OPT)
self.optState.loadSettings()
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
self.setWindowTitle("Export Project")
self.setLayout(self.outerBox)
self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"export.svg"))
self.svgGradient = QSvgWidget(self.gradPath)
self.svgGradient.setFixedSize(QSize(64,64))
self.theProject.countStatus()
self.tabMain = GuiExportMain(self.theParent, self.theProject, self.optState)
self.tabPandoc = GuiExportPandoc(self.theParent, self.theProject, self.optState)
self.tabWidget = QTabWidget()
self.tabWidget.addTab(self.tabMain, "Settings")
self.tabWidget.addTab(self.tabPandoc, "Pandoc")
self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
self.doExportForm = QGridLayout()
self.doExportForm.setContentsMargins(10,5,0,10)
self.exportButton = QPushButton("Export")
self.exportButton.clicked.connect(self._doExport)
self.closeButton = QPushButton("Close")
self.closeButton.clicked.connect(self._doClose)
self.exportStatus = QLabel("Ready ...")
self.exportProgress = QProgressBar(self)
self.doExportForm.addWidget(self.exportStatus, 0, 0, 1, 3)
self.doExportForm.addWidget(self.exportProgress, 1, 0)
self.doExportForm.addWidget(self.exportButton, 1, 1)
self.doExportForm.addWidget(self.closeButton, 1, 2)
self.innerBox.addWidget(self.tabWidget)
self.innerBox.addLayout(self.doExportForm)
self.rejected.connect(self._doClose)
self.show()
logger.debug("GuiExport initialisation complete")
return
##
# Buttons
##
def _doExport(self):
logger.verbose("GuiExport export button clicked")
wNovel = self.tabMain.expNovel.isChecked()
wNotes = self.tabMain.expNotes.isChecked()
eFormat = self.tabMain.outputFormat.currentData()
fixWidth = self.tabMain.fixedWidth.value()
wComments = self.tabMain.expComments.isChecked()
wKeywords = self.tabMain.expKeywords.isChecked()
chFormat = self.tabMain.chapterFormat.text()
unFormat = self.tabMain.unnumFormat.text()
scFormat = self.tabMain.sceneFormat.text()
seFormat = self.tabMain.sectionFormat.text()
saveTo = self.tabMain.exportPath.text()
hScene = self.tabMain.hideScene.isChecked()
hSection = self.tabMain.hideSection.isChecked()
pFormat = self.tabPandoc.outputFormat.currentData()
tFormat = GuiExportPandoc.FMT_VIA[pFormat]
if saveTo.startswith("~"):
saveTo = path.expanduser(saveTo)
exportDir = path.dirname(saveTo)
if not path.isdir(exportDir):
self.theParent.makeAlert("The export folder does not exist.",nwAlert.ERROR)
self.exportStatus.setText("Export failed ...")
return False
nItems = len(self.theProject.treeOrder)
if eFormat == GuiExportMain.FMT_PDOC:
nItems += int(0.2*nItems)
self.exportProgress.setMinimum(0)
self.exportProgress.setMaximum(nItems)
self.exportProgress.setValue(0)
if not wNovel and not wNotes:
self.exportStatus.setText("Nothing to export ...")
return False
outFile = None
if eFormat == GuiExportMain.FMT_TXT:
outFile = TextFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_MD:
outFile = MarkdownFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_HTML:
outFile = HtmlFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_TEX:
outFile = LaTeXFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_NWD:
outFile = ConcatFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_PDOC:
if tFormat == "html":
outFile = HtmlFile(self.theProject, self.theParent)
elif tFormat == "markdown":
outFile = MarkdownFile(self.theProject, self.theParent)
if outFile is None:
return False
if outFile.openFile(saveTo):
outFile.setComments(wComments)
outFile.setKeywords(wKeywords)
outFile.setExportNovel(wNovel)
outFile.setExportNotes(wNotes)
outFile.setWordWrap(fixWidth)
outFile.setChapterFormat(chFormat)
outFile.setUnNumberedFormat(unFormat)
outFile.setSceneFormat(scFormat, hScene)
outFile.setSectionFormat(seFormat, hSection)
else:
self.exportStatus.setText("Failed to open file for writing ...")
return False
time.sleep(0.5)
nDone = 0
for tHandle in self.theProject.treeOrder:
self.exportProgress.setValue(nDone)
tItem = self.theProject.getItem(tHandle)
self.exportStatus.setText("Exporting: %s" % tItem.itemName)
logger.verbose("Exporting: %s" % tItem.itemName)
if tItem is not None and tItem.itemType == nwItemType.FILE:
outFile.addText(tHandle)
nDone += 1
outFile.closeFile()
self.exportProgress.setValue(nDone)
self.exportStatus.setText("Export to %s complete" % outFile.fileName)
logger.verbose("Export to %s complete" % outFile.fileName)
if eFormat == GuiExportMain.FMT_TEX:
# Check that encoding was successful
if outFile.texCodecFail:
self.theParent.makeAlert((
"Failed to escape unicode characters while writing LaTeX file. The generated "
".tex file may not build properly. Make sure the python package '{package:s}' "
"is installed and working."
).format(
package = packageRefURL("latexcodec")
), nwAlert.WARN)
if eFormat != GuiExportMain.FMT_PDOC:
return True
# If we've reached this point, we're also running Pandoc
if self._callPandoc(saveTo, tFormat, pFormat):
self.exportProgress.setValue(nItems)
self.exportStatus.setText("Pandoc conversion complete")
logger.verbose("Pandoc conversion complete")
else:
self.exportProgress.setValue(nItems)
self.exportStatus.setText("Pandoc conversion failed")
logger.verbose("Pandoc conversion failed")
return False
return True
def _callPandoc(self, inFile, inFmt, outFmt):
pFmt = {
GuiExportPandoc.FMT_ODT : "odt",
GuiExportPandoc.FMT_DOCX : "docx",
GuiExportPandoc.FMT_EPUB2 : "epub2",
GuiExportPandoc.FMT_EPUB3 : "epub3",
GuiExportPandoc.FMT_ZIM : "zimwiki",
}
try:
import pypandoc
except:
self.theParent.makeAlert((
"Could not load the '{package:s}' package. "
"Make sure it is installed, and try again."
).format(
package = packageRefURL("pypandoc")
), nwAlert.ERROR)
return False
outFile = path.splitext(inFile)[0]+GuiExportPandoc.FMT_EXT[outFmt]
fileName = path.basename(outFile)
if path.isfile(outFile) and self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self.theParent, "Overwrite",
("File '%s' already exists.<br>Do you want to overwrite it?" % fileName)
)
if msgRes != QMessageBox.Yes:
return False
try:
pypandoc.convert_file(
source_file = inFile,
format = inFmt,
outputfile = outFile,
to = pFmt[outFmt],
extra_args = (),
encoding = "utf-8",
filters = None
)
except Exception as e:
self.theParent.makeAlert(
["Failed to convert file using pypandoc + Pandoc.",
str(e)], nwAlert.ERROR
)
return False
return True
def _doClose(self):
logger.verbose("GuiExport close button clicked")
# General Settings
wNovel = self.tabMain.expNovel.isChecked()
wNotes = self.tabMain.expNotes.isChecked()
eFormat = self.tabMain.outputFormat.currentData()
fixWidth = self.tabMain.fixedWidth.value()
wComments = self.tabMain.expComments.isChecked()
wKeywords = self.tabMain.expKeywords.isChecked()
chFormat = self.tabMain.chapterFormat.text()
unFormat = self.tabMain.unnumFormat.text()
scFormat = self.tabMain.sceneFormat.text()
seFormat = self.tabMain.sectionFormat.text()
saveTo = self.tabMain.exportPath.text()
hScene = self.tabMain.hideScene.isChecked()
hSection = self.tabMain.hideSection.isChecked()
if saveTo.startswith("~"):
saveTo = path.expanduser(saveTo)
self.optState.setSetting("wNovel", wNovel)
self.optState.setSetting("wNotes", wNotes)
self.optState.setSetting("eFormat", eFormat)
self.optState.setSetting("fixWidth", fixWidth)
self.optState.setSetting("wComments",wComments)
self.optState.setSetting("wKeywords",wKeywords)
self.optState.setSetting("chFormat", chFormat)
self.optState.setSetting("unFormat", unFormat)
self.optState.setSetting("scFormat", scFormat)
self.optState.setSetting("seFormat", seFormat)
self.optState.setSetting("saveTo", saveTo)
self.optState.setSetting("hScene", hScene)
self.optState.setSetting("hSection", hSection)
# Pandoc Settings
pFormat = self.tabPandoc.outputFormat.currentData()
self.optState.setSetting("pFormat", pFormat)
self.optState.saveSettings()
self.close()
return
# END Class GuiExport
class GuiExportMain(QWidget):
FMT_NWD = 1 # novelWriter markdown
FMT_TXT = 2 # Plain text file
FMT_MD = 3 # Markdown file
FMT_HTML = 4 # HTML file
FMT_TEX = 5 # LaTeX file
FMT_PDOC = 6 # Pass to pandoc
FMT_EXT = {
FMT_NWD : ".nwd",
FMT_TXT : ".txt",
FMT_MD : ".md",
FMT_HTML : ".htm",
FMT_TEX : ".tex",
FMT_PDOC : ".tmp",
}
FMT_HELP = {
FMT_NWD : (
"Exports a document using the novelWriter markdown format. The files selected by the "
"filters are appended as-is, including comments and other settings."
),
FMT_TXT : (
"Exports a plain text file. All formatting is stripped and comments are in square "
"brackets."
),
FMT_MD : (
"Exports a standard markdown file. Comments are converted to preformatted text blocks."
),
FMT_HTML : (
"Exports a plain html5 file. Comments are wrapped in blocks with a yellow background "
"colour."
),
FMT_TEX : (
"Exports a LaTeX file that can be compiled to PDF using for instance PDFLaTeX. "
"Comments are exported as LaTeX comments."
),
FMT_PDOC : (
"Exports first to markdown or html5. The file is then passed on to Pandoc for a second "
"stage. Use the Pandoc tab for settings up the conversion."
),
}
def __init__(self, theParent, theProject, optState):
QWidget.__init__(self, theParent)
self.theParent = theParent
self.theProject = theProject
self.theTheme = theParent.theTheme
self.outerBox = QGridLayout()
self.optState = optState
self.currFormat = self.FMT_TXT
# Select Files
self.guiFiles = QGroupBox("Selection", self)
self.guiFilesForm = QGridLayout(self)
self.guiFiles.setLayout(self.guiFilesForm)
self.expNovel = QCheckBox("Novel files",self)
self.expNovel.setChecked(self.optState.getSetting("wNovel"))
self.expNovel.setToolTip("Include all novel files in the exported document")
self.expNotes = QCheckBox("Note files",self)
self.expNotes.setChecked(self.optState.getSetting("wNotes"))
self.expNotes.setToolTip("Include all note files in the exported document")
self.expComments = QCheckBox("Comments",self)
self.expComments.setChecked(self.optState.getSetting("wComments"))
self.expComments.setToolTip("Export comments from all files")
self.expKeywords = QCheckBox("Keywords",self)
self.expKeywords.setChecked(self.optState.getSetting("wKeywords"))
self.expKeywords.setToolTip("Export @keywords from all files")
self.guiFilesForm.addWidget(self.expNovel, 0, 1)
self.guiFilesForm.addWidget(self.expComments, 0, 2)
self.guiFilesForm.addWidget(self.expNotes, 1, 1)
self.guiFilesForm.addWidget(self.expKeywords, 1, 2)
self.guiFilesForm.setRowStretch(2, 1)
# Chapter Settings
self.guiChapters = QGroupBox("Chapter Headings", self)
self.guiChaptersForm = QGridLayout(self)
self.guiChapters.setLayout(self.guiChaptersForm)
self.chapterFormat = QLineEdit()
self.chapterFormat.setText(self.optState.getSetting("chFormat"))
self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%")
self.chapterFormat.setMinimumWidth(250)
self.unnumFormat = QLineEdit()
self.unnumFormat.setText(self.optState.getSetting("unFormat"))
self.unnumFormat.setToolTip("Available formats: %title%")
self.unnumFormat.setMinimumWidth(250)
self.guiChaptersForm.addWidget(QLabel("Numbered"), 0, 0)
self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1)
self.guiChaptersForm.addWidget(QLabel("Unnumbered"), 1, 0)
self.guiChaptersForm.addWidget(self.unnumFormat, 1, 1)
# Scene and Section Settings
self.guiScenes = QGroupBox("Other Headings", self)
self.guiScenesForm = QGridLayout(self)
self.guiScenes.setLayout(self.guiScenesForm)
self.sceneFormat = QLineEdit()
self.sceneFormat.setText(self.optState.getSetting("scFormat"))
self.sceneFormat.setToolTip("Available formats: %title%")
self.sceneFormat.setMinimumWidth(100)
self.sectionFormat = QLineEdit()
self.sectionFormat.setText(self.optState.getSetting("seFormat"))
self.sectionFormat.setToolTip("Available formats: %title%")
self.sectionFormat.setMinimumWidth(100)
self.hideScene = QCheckBox("Skip",self)
self.hideScene.setChecked(self.optState.getSetting("hScene"))
self.hideScene.setToolTip("Skip scene titles in export")
self.hideSection = QCheckBox("Skip",self)
self.hideSection.setChecked(self.optState.getSetting("hSection"))
self.hideSection.setToolTip("Skip section titles in export")
self.guiScenesForm.addWidget(QLabel("Scenes"), 0, 0)
self.guiScenesForm.addWidget(self.sceneFormat, 0, 1)
self.guiScenesForm.addWidget(self.hideScene, 0, 2)
self.guiScenesForm.addWidget(QLabel("Sections"), 1, 0)
self.guiScenesForm.addWidget(self.sectionFormat, 1, 1)
self.guiScenesForm.addWidget(self.hideSection, 1, 2)
# Output Path
self.exportTo = QGroupBox("Export Folder", self)
self.exportToForm = QGridLayout(self)
self.exportTo.setLayout(self.exportToForm)
self.exportPath = QLineEdit(self.optState.getSetting("saveTo"))
self.exportGetPath = QPushButton(self.theTheme.getIcon("folder"),"")
self.exportGetPath.clicked.connect(self._exportFolder)
self.exportToForm.addWidget(QLabel("Save to"), 0, 0)
self.exportToForm.addWidget(self.exportPath, 0, 1)
self.exportToForm.addWidget(self.exportGetPath, 0, 2)
# Output Format
self.guiOutput = QGroupBox("Export", self)
self.guiOutputForm = QGridLayout(self)
self.guiOutput.setLayout(self.guiOutputForm)
self.outputHelp = QLabel("")
self.outputHelp.setWordWrap(True)
self.outputHelp.setMinimumHeight(55)
self.outputHelp.setAlignment(Qt.AlignTop)
self.outputFormat = QComboBox(self)
self.outputFormat.addItem("novelWriter Markdown (.nwd)", self.FMT_NWD)
self.outputFormat.addItem("Plain Text (.txt)", self.FMT_TXT)
self.outputFormat.addItem("Markdown (.md)", self.FMT_MD)
self.outputFormat.addItem("HTML5 (.htm)", self.FMT_HTML)
self.outputFormat.addItem("LaTeX for PDF (.tex)", self.FMT_TEX)
self.outputFormat.addItem("Pandoc via Markdown or HTML", self.FMT_PDOC)
self.outputFormat.currentIndexChanged.connect(self._updateFormat)
optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat"))
if optIdx == -1:
self.outputFormat.setCurrentIndex(1)
self._updateFormat(1)
else:
self.outputFormat.setCurrentIndex(optIdx)
self._updateFormat(optIdx)
self.guiOutputForm.addWidget(QLabel("Format"), 0, 0)
self.guiOutputForm.addWidget(self.outputFormat, 0, 1)
self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3)
self.guiOutputForm.setColumnStretch(2, 1)
# Additional Settings
self.addSettings = QGroupBox("Additional Settings (Format Dependent)", self)
self.addSettingsForm = QGridLayout(self)
self.addSettings.setLayout(self.addSettingsForm)
self.fixedWidth = QSpinBox(self)
self.fixedWidth.setMinimum(0)
self.fixedWidth.setMaximum(999)
self.fixedWidth.setSingleStep(1)
self.fixedWidth.setValue(self.optState.getSetting("fixWidth"))
self.fixedWidth.setToolTip("Applies to .txt and .md files. A value of '0' disables the feature.")
self.addSettingsForm.addWidget(QLabel("Fixed width"), 0, 0)
self.addSettingsForm.addWidget(self.fixedWidth, 0, 1)
self.addSettingsForm.setColumnStretch(2, 1)
# Assemble
self.outerBox.addWidget(self.guiOutput, 0, 0, 1, 2)
self.outerBox.addWidget(self.guiFiles, 0, 2)
self.outerBox.addWidget(self.guiChapters, 1, 0, 1, 2)
self.outerBox.addWidget(self.guiScenes, 1, 2)
self.outerBox.addWidget(self.addSettings, 2, 0, 1, 3)
self.outerBox.addWidget(self.exportTo, 3, 0, 1, 3)
self.outerBox.setColumnStretch(0, 1)
self.outerBox.setColumnStretch(1, 1)
self.outerBox.setColumnStretch(2, 1)
self.setLayout(self.outerBox)
return
##
# Internal Functions
##
def _updateFormat(self, currIdx):
"""Update help text under output format selection and file extension in file box
"""
if currIdx == -1:
self.outputHelp.setText("")
else:
self.currFormat = self.outputFormat.itemData(currIdx)
self.outputHelp.setText("<i>%s</i>" % self.FMT_HELP[self.currFormat])
self._checkFileExtension()
return
def _exportFolder(self):
currDir = self.exportPath.text()
if not path.isdir(currDir):
currDir = ""
extFilter = [
"novelWriter document files (*.nwd)",
"Text files (*.txt)",
"Markdown files (*.md)",
"HTML files (*.htm *.html)",
"LaTeX files (*.tex)",
"All files (*.*)",
]
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName(
self,"Export File",self.exportPath.text(),options=dlgOpt,filter=";;".join(extFilter)
)
if saveTo:
self.exportPath.setText(saveTo[0])
self._checkFileExtension()
return True
return False
def _checkFileExtension(self):
saveTo = self.exportPath.text()
if saveTo.startswith("~"):
saveTo = path.expanduser(saveTo)
fileBits = path.splitext(saveTo)
if self.currFormat > 0 and fileBits[0].strip() != "":
saveTo = fileBits[0]+self.FMT_EXT[self.currFormat]
self.exportPath.setText(saveTo)
return
# END Class GuiExportMain
class GuiExportPandoc(QWidget):
FMT_ODT = 1
FMT_DOCX = 2
FMT_EPUB2 = 4
FMT_EPUB3 = 5
FMT_ZIM = 6
FMT_EXT = {
FMT_ODT : ".odt",
FMT_DOCX : ".docx",
FMT_EPUB2 : ".epub",
FMT_EPUB3 : ".epub",
FMT_ZIM : ".txt",
}
FMT_VIA = {
FMT_ODT : "html",
FMT_DOCX : "html",
FMT_EPUB2 : "markdown",
FMT_EPUB3 : "markdown",
FMT_ZIM : "markdown",
}
def __init__(self, theParent, theProject, optState):
QWidget.__init__(self, theParent)
self.theParent = theParent
self.theProject = theProject
self.outerBox = QGridLayout()
self.optState = optState
try:
import pypandoc
self.hasPyPan = True
except:
self.hasPyPan = False
# Information
self.guiInfo = QGroupBox("Information", self)
self.guiInfoBox = QVBoxLayout(self)
self.guiInfo.setLayout(self.guiInfoBox)
self.infoHelp = QLabel("")
self.infoHelp.setWordWrap(True)
self.infoHelp.setMinimumHeight(55)
self.infoHelp.setAlignment(Qt.AlignTop)
self.guiInfoBox.addWidget(self.infoHelp)
if self.hasPyPan:
self.infoHelp.setText((
"Additional export to other document formats than in the Settings tab is provided "
"by Pandoc. the project is first exported to Markdown or HTML, depending on final "
"format, and then processed by Pandoc into the desired format."
))
else:
self.infoHelp.setText((
"The Python package 'pypandoc' is not installed or isn't working. This package is "
"required for interfacing with Pandoc. Please install it before proceeding."
))
# Output Format
self.guiOutput = QGroupBox("Pandoc Format", self)
self.guiOutputForm = QGridLayout(self)
self.guiOutput.setLayout(self.guiOutputForm)
self.outputFormat = QComboBox(self)
self.outputFormat.addItem("Open Office Document (.odt)", self.FMT_ODT)
self.outputFormat.addItem("Word Document (.docx)", self.FMT_DOCX)
self.outputFormat.addItem("ePUB eBook v2 (.epub2)", self.FMT_EPUB2)
self.outputFormat.addItem("ePUB eBook v3 (.epub3)", self.FMT_EPUB3)
self.outputFormat.addItem("Zim Wiki (.txt)", self.FMT_ZIM)
# self.outputFormat.currentIndexChanged.connect(self._updateFormat)
optIdx = self.outputFormat.findData(self.optState.getSetting("pFormat"))
if optIdx == -1:
self.outputFormat.setCurrentIndex(1)
else:
self.outputFormat.setCurrentIndex(optIdx)
self.guiOutputForm.addWidget(QLabel("Format"), 0, 0)
self.guiOutputForm.addWidget(self.outputFormat, 0, 1)
self.guiOutputForm.setColumnStretch(2, 1)
# Assemble
self.outerBox.addWidget(self.guiInfo, 0, 0)
self.outerBox.addWidget(self.guiOutput, 1, 0)
self.outerBox.setRowStretch(2, 1)
# self.outerBox.setColumnStretch(0, 1)
# self.outerBox.setColumnStretch(1, 1)
# self.outerBox.setColumnStretch(2, 1)
self.setLayout(self.outerBox)
return
# END Class GuiExportPandoc
class ExportLastState(OptLastState):
def __init__(self, theProject, theFile):
OptLastState.__init__(self, theProject, theFile)
self.theState = {
"wNovel" : True,
"wNotes" : False,
"eFormat" : 1,
"pFormat" : 1,
"fixWidth" : 80,
"wComments" : False,
"wKeywords" : False,
"chFormat" : "Chapter %numword%",
"unFormat" : "%title%",
"scFormat" : "* * *",
"seFormat" : "",
"saveTo" : "",
"hScene" : False,
"hSection" : False,
}
self.stringOpt = ("chFormat","unFormat","scFormat","seFormat","saveTo")
self.boolOpt = ("wNovel","wNotes","wComments","wKeywords","hScene","hSection")
self.intOpt = ("eFormat","pFormat","fixWidth")
return
# END Class ExportLastState
+145
View File
@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Item Editor
novelWriter GUI Item Editor
===============================
Class holding the item editor
File History:
Created: 2019-04-27 [0.0.1]
"""
import logging
import nw
from os import path
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QGroupBox, QFormLayout, QLineEdit, QPushButton, QComboBox
from PyQt5.QtSvg import QSvgWidget
from nw.enum import nwItemLayout, nwItemClass, nwItemType
from nw.constants import nwLabels
logger = logging.getLogger(__name__)
class GuiItemEditor(QDialog):
def __init__(self, theParent, theProject, tHandle):
QDialog.__init__(self, theParent)
logger.debug("Initialising ItemEditor ...")
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.theItem = self.theProject.getItem(tHandle)
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
self.setWindowTitle("Item Settings")
self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"gear.svg"))
self.svgGradient = QSvgWidget(self.gradPath)
self.svgGradient.setFixedSize(QSize(64,64))
self.setLayout(self.outerBox)
self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
self.mainGroup = QGroupBox("Item Settings")
self.mainForm = QFormLayout()
self.editName = QLineEdit()
self.editStatus = QComboBox()
self.editLayout = QComboBox()
if self.theItem.itemClass == nwItemClass.NOVEL:
for sLabel, _, _ in self.theProject.statusItems:
self.editStatus.addItem(
self.theParent.statusIcons[sLabel], sLabel, sLabel
)
else:
for sLabel, _, _ in self.theProject.importItems:
self.editStatus.addItem(
self.theParent.importIcons[sLabel], sLabel, sLabel
)
self.validLayouts = []
if self.theItem.itemType == nwItemType.FILE:
if self.theItem.itemClass == nwItemClass.NOVEL:
self.validLayouts.append(nwItemLayout.TITLE)
self.validLayouts.append(nwItemLayout.BOOK)
self.validLayouts.append(nwItemLayout.PAGE)
self.validLayouts.append(nwItemLayout.PARTITION)
self.validLayouts.append(nwItemLayout.UNNUMBERED)
self.validLayouts.append(nwItemLayout.CHAPTER)
self.validLayouts.append(nwItemLayout.SCENE)
self.validLayouts.append(nwItemLayout.NOTE)
else:
self.validLayouts.append(nwItemLayout.NOTE)
else:
self.validLayouts.append(nwItemLayout.NO_LAYOUT)
for itemLayout in nwItemLayout:
if itemLayout in self.validLayouts:
self.editLayout.addItem(nwLabels.LAYOUT_NAME[itemLayout],itemLayout)
self.mainForm.addRow("Label", self.editName)
self.mainForm.addRow("Status", self.editStatus)
self.mainForm.addRow("Layout", self.editLayout)
self.editName.setMinimumWidth(200)
self.editName.setText(self.theItem.itemName)
statusIdx = self.editStatus.findData(self.theItem.itemStatus)
if statusIdx != -1:
self.editStatus.setCurrentIndex(statusIdx)
layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
if layoutIdx != -1:
self.editLayout.setCurrentIndex(layoutIdx)
self.buttonBox = QHBoxLayout()
self.closeButton = QPushButton("Close")
self.closeButton.clicked.connect(self._doClose)
self.saveButton = QPushButton("Save")
self.saveButton.setDefault(True)
self.saveButton.clicked.connect(self._doSave)
self.buttonBox.addStretch(1)
self.buttonBox.addWidget(self.closeButton)
self.buttonBox.addWidget(self.saveButton)
self.mainGroup.setLayout(self.mainForm)
self.innerBox.addWidget(self.mainGroup)
self.innerBox.addLayout(self.buttonBox)
self.show()
self.editName.selectAll()
logger.debug("ItemEditor initialisation complete")
return
def _doSave(self):
logger.verbose("ItemEditor save button clicked")
itemName = self.editName.text()
itemStatus = self.editStatus.currentData()
itemLayout = self.editLayout.currentData()
self.theItem.setName(itemName)
self.theItem.setStatus(itemStatus)
self.theItem.setLayout(itemLayout)
self.theProject.setProjectChanged(True)
self.accept()
self.close()
return
def _doClose(self):
logger.verbose("ItemEditor close button clicked")
self.reject()
self.close()
return
# END Class GuiItemEditor
+446
View File
@@ -0,0 +1,446 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Project Editor
novelWriter GUI Project Editor
===================================
Class holding the project editor
File History:
Created: 2018-09-29 [0.0.1]
"""
import logging
import nw
from os import path
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel,
QWidget, QTabWidget, QDialogButtonBox, QListWidget, QListWidgetItem, QPushButton,
QColorDialog, QAbstractItemView, QTreeWidget, QTreeWidgetItem, QCheckBox
)
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
class GuiProjectEditor(QDialog):
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising ProjectEditor ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
self.setWindowTitle("Project Settings")
self.setLayout(self.outerBox)
self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"gear.svg"))
self.svgGradient = QSvgWidget(self.gradPath)
self.svgGradient.setFixedSize(QSize(64,64))
self.theProject.countStatus()
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
self.tabStatus = GuiProjectEditStatus(self.theParent, self.theProject.statusItems)
self.tabImport = GuiProjectEditStatus(self.theParent, self.theProject.importItems)
self.tabReplace = GuiProjectEditReplace(self.theParent, self.theProject)
self.tabWidget = QTabWidget()
self.tabWidget.addTab(self.tabMain, "Settings")
self.tabWidget.addTab(self.tabStatus, "Status")
self.tabWidget.addTab(self.tabImport, "Importance")
self.tabWidget.addTab(self.tabReplace,"Auto-Replace")
self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
self.innerBox.addWidget(self.tabWidget)
self.innerBox.addWidget(self.buttonBox)
self.show()
logger.debug("ProjectEditor initialisation complete")
return
def _doSave(self):
logger.verbose("ProjectEditor save button clicked")
projName = self.tabMain.editName.text()
bookTitle = self.tabMain.editTitle.text()
bookAuthors = self.tabMain.editAuthors.toPlainText()
doBackup = self.tabMain.doBackup.isChecked()
self.theProject.setProjectName(projName)
self.theProject.setBookTitle(bookTitle)
self.theProject.setBookAuthors(bookAuthors)
self.theProject.setProjBackup(doBackup)
if self.tabStatus.colChanged:
statusCol = self.tabStatus.getNewList()
self.theProject.setStatusColours(statusCol)
if self.tabImport.colChanged:
importCol = self.tabImport.getNewList()
self.theProject.setImportColours(importCol)
if self.tabStatus.colChanged or self.tabImport.colChanged:
self.theParent.rebuildTree()
if self.tabReplace.arChanged:
newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList)
self.close()
return
def _doClose(self):
logger.verbose("ProjectEditor close button clicked")
self.close()
return
# END Class GuiProjectEditor
class GuiProjectEditMain(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
self.theParent = theParent
self.theProject = theProject
self.mainForm = QFormLayout()
self.backupBox = QHBoxLayout()
self.editName = QLineEdit()
self.editTitle = QLineEdit()
self.editAuthors = QPlainTextEdit()
self.doBackup = QCheckBox(self)
self.mainForm.addRow("Working Title", self.editName)
self.mainForm.addRow("Book Title", self.editTitle)
self.mainForm.addRow("Book Authors", self.editAuthors)
self.mainForm.addRow(self.backupBox)
self.backupBox.addStretch(1)
self.backupBox.addWidget(QLabel("Backup on Close"))
self.backupBox.addWidget(self.doBackup)
self.editName.setText(self.theProject.projName)
self.editTitle.setText(self.theProject.bookTitle)
bookAuthors = ""
for bookAuthor in self.theProject.bookAuthors:
bookAuthors += bookAuthor+"\n"
self.editAuthors.setPlainText(bookAuthors)
if self.theProject.doBackup:
self.doBackup.setCheckState(Qt.Checked)
else:
self.doBackup.setCheckState(Qt.Unchecked)
self.setLayout(self.mainForm)
self.editAuthors.setMaximumHeight(120)
return
# END Class GuiProjectEditMain
class GuiProjectEditStatus(QWidget):
def __init__(self, theParent, theStatus):
QWidget.__init__(self, theParent)
self.theParent = theParent
self.theStatus = theStatus
self.colData = []
self.colCounts = []
self.colChanged = False
self.selColour = None
self.mainBox = QHBoxLayout()
self.mainForm = QVBoxLayout()
self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.model().rowsMoved.connect(self._rowsMoved)
for iName, iCol, nUse in self.theStatus:
self._addItem(iName, iCol, iName, nUse)
self.editName = QLineEdit()
self.editName.setEnabled(False)
self.newButton = QPushButton("New")
self.delButton = QPushButton("Delete")
self.saveButton = QPushButton("Save")
self.colPixmap = QPixmap(16,16)
self.colPixmap.fill(QColor(120,120,120))
self.colButton = QPushButton(QIcon(self.colPixmap),"Colour")
self.colButton.setIconSize(self.colPixmap.rect().size())
self.newButton.clicked.connect(self._newItem)
self.delButton.clicked.connect(self._delItem)
self.saveButton.clicked.connect(self._saveItem)
self.colButton.clicked.connect(self._selectColour)
self.mainForm.addWidget(self.newButton)
self.mainForm.addWidget(self.delButton)
self.mainForm.addStretch(1)
self.mainForm.addWidget(QLabel("<b>Name</b>"))
self.mainForm.addWidget(self.editName)
self.mainForm.addWidget(self.colButton)
self.mainForm.addStretch(1)
self.mainForm.addWidget(self.saveButton)
self.mainBox.addWidget(self.listBox)
self.mainBox.addLayout(self.mainForm)
self.setLayout(self.mainBox)
return
def getNewList(self):
if self.colChanged:
newList = []
for n in range(self.listBox.count()):
nItem = self.listBox.item(n)
nIdx = nItem.data(Qt.UserRole)
newList.append(self.colData[nIdx])
return newList
return None
##
# User Actions
##
def _selectColour(self):
logger.verbose("Item colour button clicked")
if self.selColour is not None:
newCol = QColorDialog.getColor(self.selColour, self, "Select Colour", QColorDialog.DontUseNativeDialog)
if newCol:
self.selColour = newCol
colPixmap = QPixmap(16,16)
colPixmap.fill(newCol)
self.colButton.setIcon(QIcon(colPixmap))
self.colButton.setIconSize(colPixmap.rect().size())
return
def _newItem(self):
logger.verbose("New item button clicked")
newItem = self._addItem("New Item", (0, 0, 0), None, 0)
newItem.setBackground(QBrush(QColor(0,255,0,80)))
self.colChanged = True
return
def _delItem(self):
logger.verbose("Delete item button clicked")
selItem = self._getSelectedItem()
if selItem is not None:
iRow = self.listBox.row(selItem)
selIdx = selItem.data(Qt.UserRole)
if self.colCounts[selIdx] == 0:
self.listBox.takeItem(iRow)
self.colChanged = True
else:
self.theParent.makeAlert("Cannot delete status item that is in use.",nwAlert.ERROR)
return
def _saveItem(self):
logger.verbose("Save item button clicked")
selItem = self._getSelectedItem()
iRow = self.listBox.row(selItem)
if selItem is not None:
selIdx = selItem.data(Qt.UserRole)
self.colData[selIdx] = (
self.editName.text().strip(),
self.selColour.red(),
self.selColour.green(),
self.selColour.blue(),
self.colData[selIdx][4]
)
selItem.setText("%s [%d]" % (self.colData[selIdx][0], self.colCounts[selIdx]))
selItem.setIcon(self.colButton.icon())
self.editName.setEnabled(False)
self.colChanged = True
return
def _addItem(self, iName, iCol, oName, nUse):
newIcon = QPixmap(16,16)
newIcon.fill(QColor(*iCol))
newItem = QListWidgetItem()
newItem.setText("%s [%d]" % (iName, nUse))
newItem.setIcon(QIcon(newIcon))
newItem.setData(Qt.UserRole, len(self.colData))
self.listBox.addItem(newItem)
self.colData.append((iName,*iCol,oName))
self.colCounts.append(nUse)
return newItem
def _selectedItem(self):
logger.verbose("Item selected")
selItem = self._getSelectedItem()
if selItem is not None:
selIdx = selItem.data(Qt.UserRole)
selVal = self.colData[selIdx]
self.selColour = QColor(selVal[1],selVal[2],selVal[3])
newIcon = QPixmap(16,16)
newIcon.fill(self.selColour)
self.editName.setText(selVal[0])
self.colButton.setIcon(QIcon(newIcon))
self.editName.setEnabled(True)
self.editName.selectAll()
self.editName.setFocus()
return
##
# Internal Functions
##
def _getSelectedItem(self):
selItem = self.listBox.selectedItems()
if len(selItem) == 0:
return None
if isinstance(selItem[0], QListWidgetItem):
return selItem[0]
return None
def _rowsMoved(self):
logger.verbose("A drag move event occurred")
self.colChanged = True
return
# END Class GuiProjectEditStatus
class GuiProjectEditReplace(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theProject
self.arChanged = False
self.outerBox = QVBoxLayout()
self.bottomBox = QHBoxLayout()
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Keyword","Replace With"])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setIndentation(0)
for aKey, aVal in self.theProject.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem)
self.listBox.sortByColumn(0, Qt.AscendingOrder)
self.listBox.setSortingEnabled(True)
self.editKey = QLineEdit()
self.editValue = QLineEdit()
self.saveButton = QPushButton(self.theTheme.getIcon("save"),"")
self.addButton = QPushButton(self.theTheme.getIcon("add"),"")
self.delButton = QPushButton(self.theTheme.getIcon("remove"),"")
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.saveButton.clicked.connect(self._saveEntry)
self.addButton.clicked.connect(self._addEntry)
self.delButton.clicked.connect(self._delEntry)
self.bottomBox.addWidget(self.editKey, 2)
self.bottomBox.addWidget(self.editValue, 3)
self.bottomBox.addWidget(self.saveButton)
self.bottomBox.addWidget(self.addButton)
self.bottomBox.addWidget(self.delButton)
self.outerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.bottomBox)
self.setLayout(self.outerBox)
return
def getNewList(self):
newList = {}
for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n)
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
newList[aKey] = aVal
return newList
##
# Internal Functions
##
def _selectedItem(self):
selItem = self._getSelectedItem()
if selItem is None:
return False
editKey = self._stripNotAllowed(selItem.text(0))
editVal = selItem.text(1)
self.editKey.setText(editKey)
self.editValue.setText(editVal)
self.editKey.setEnabled(True)
self.editValue.setEnabled(True)
self.editKey.selectAll()
self.editKey.setFocus()
return True
def _saveEntry(self):
selItem = self._getSelectedItem()
if selItem is None:
return False
newKey = self.editKey.text()
newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0:
selItem.setText(0,"<%s>" % saveKey)
selItem.setText(1,newVal)
self.editKey.clear()
self.editValue.clear()
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self.arChanged = True
return
def _addEntry(self):
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
newVal = ""
newItem = QTreeWidgetItem([saveKey, newVal])
self.listBox.addTopLevelItem(newItem)
return True
def _delEntry(self):
selItem = self._getSelectedItem()
if selItem is None:
return False
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.arChanged = True
return True
def _getSelectedItem(self):
selItem = self.listBox.selectedItems()
if len(selItem) == 0:
return None
return selItem[0]
def _stripNotAllowed(self, theKey):
retKey = ""
for c in theKey:
if c.isalnum():
retKey += c
return retKey
# END Class GuiProjectEditReplace
+249
View File
@@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Session Log Viewer
novelWriter GUI Session Log Viewer
======================================
Class holding the session log view window
File History:
Created: 2019-10-20 [0.3]
"""
import logging
import nw
from os import path
from datetime import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor, QPixmap, QFont
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QHeaderView,
QGridLayout, QLabel, QGroupBox, QCheckBox
)
from nw.tools.optlaststate import OptLastState
from nw.constants import nwConst, nwFiles
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
class GuiSessionLogView(QDialog):
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising SessionLogView ...")
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.optState = SessionLogLastState(self.theProject,nwFiles.SLOG_OPT)
self.optState.loadSettings()
self.timeFilter = 0.0
self.timeTotal = 0.0
self.outerBox = QGridLayout()
self.bottomBox = QHBoxLayout()
self.setWindowTitle("Session Log")
self.setMinimumWidth(420)
self.setMinimumHeight(400)
widthCol0 = self.optState.validIntRange(self.optState.getSetting("widthCol0"), 30, 999, 180)
widthCol1 = self.optState.validIntRange(self.optState.getSetting("widthCol1"), 30, 999, 80)
widthCol2 = self.optState.validIntRange(self.optState.getSetting("widthCol2"), 30, 999, 80)
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Session Start","Length","Words",""])
self.listBox.setIndentation(0)
self.listBox.setColumnWidth(0,widthCol0)
self.listBox.setColumnWidth(1,widthCol1)
self.listBox.setColumnWidth(2,widthCol2)
self.listBox.setColumnWidth(3,0)
hHeader = self.listBox.headerItem()
hHeader.setTextAlignment(1,Qt.AlignRight)
hHeader.setTextAlignment(2,Qt.AlignRight)
self.monoFont = QFont("Monospace",10)
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder)
sortCol = self.optState.validIntRange(self.optState.getSetting("sortCol"), 0, 2, 0)
sortOrder = self.optState.validIntTuple(self.optState.getSetting("sortOrder"), sortValid, Qt.DescendingOrder)
self.listBox.sortByColumn(sortCol, sortOrder)
self.listBox.setSortingEnabled(True)
# Session Info
self.infoBox = QGroupBox("Sum Total Time", self)
self.infoBoxForm = QGridLayout(self)
self.infoBox.setLayout(self.infoBoxForm)
self.labelTotal = QLabel(self._formatTime(0))
self.labelTotal.setFont(self.monoFont)
self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.labelFilter = QLabel(self._formatTime(0))
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)
# Filter Options
self.filterBox = QGroupBox("Filters", self)
self.filterBoxForm = QGridLayout(self)
self.filterBox.setLayout(self.filterBoxForm)
self.hideZeros = QCheckBox("Hide zero word count", self)
self.hideZeros.setChecked(self.optState.getSetting("hideZeros"))
self.hideZeros.stateChanged.connect(self._doHideZeros)
self.hideNegative = QCheckBox("Hide negative word count", self)
self.hideNegative.setChecked(self.optState.getSetting("hideNegative"))
self.hideNegative.stateChanged.connect(self._doHideNegative)
self.filterBoxForm.addWidget(self.hideZeros, 0, 0)
self.filterBoxForm.addWidget(self.hideNegative, 1, 0)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.rejected.connect(self._doClose)
# Assemble
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.setLayout(self.outerBox)
self.show()
logger.debug("SessionLogView initialisation complete")
self._loadSessionLog()
return
def _loadSessionLog(self):
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
hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked()
logger.debug("Loading session log file")
try:
with open(logFile,mode="r",encoding="utf8") as inFile:
for inLine in inFile:
inData = inLine.split()
if len(inData) != 8:
continue
dStart = datetime.strptime("%s %s" % (inData[1],inData[2]),nwConst.tStampFmt)
dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]),nwConst.tStampFmt)
nWords = int(inData[7])
tDiff = dEnd - dStart
sDiff = tDiff.total_seconds()
self.timeTotal += sDiff
if abs(nWords) > 0:
self.timeFilter += sDiff
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)
except Exception as e:
self.theParent.makeAlert(["Failed to read session log file.",str(e)], nwAlert.ERROR)
return False
self.labelFilter.setText(self._formatTime(self.timeFilter))
self.labelTotal.setText(self._formatTime(self.timeTotal))
return True
def _doClose(self):
widthCol0 = self.listBox.columnWidth(0)
widthCol1 = self.listBox.columnWidth(1)
widthCol2 = self.listBox.columnWidth(2)
sortCol = self.listBox.sortColumn()
sortOrder = self.listBox.header().sortIndicatorOrder()
hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked()
self.optState.setSetting("widthCol0", widthCol0)
self.optState.setSetting("widthCol1", widthCol1)
self.optState.setSetting("widthCol2", widthCol2)
self.optState.setSetting("sortCol", sortCol)
self.optState.setSetting("sortOrder", sortOrder)
self.optState.setSetting("hideZeros", hideZeros)
self.optState.setSetting("hideNegative",hideNegative)
self.optState.saveSettings()
self.close()
return
def _doHideZeros(self, newState):
self._loadSessionLog()
return
def _doHideNegative(self, newState):
self._loadSessionLog()
return
def _formatTime(self, tS):
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
class SessionLogLastState(OptLastState):
def __init__(self, theProject, theFile):
OptLastState.__init__(self, theProject, theFile)
self.theState = {
"widthCol0" : 180,
"widthCol1" : 80,
"widthCol2" : 80,
"sortCol" : 0,
"sortOrder" : Qt.DescendingOrder,
"hideZeros" : True,
"hideNegative" : False,
}
self.stringOpt = ()
self.boolOpt = ("hideZeros","hideNegative")
self.intOpt = ("widthCol0","widthCol1","widthCol2","sortCol","sortOrder")
return
# END Class SessionLogLastState
+268
View File
@@ -0,0 +1,268 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Timeline View
novelWriter GUI Timeline View
=================================
Class holding the timeline view window
File History:
Created: 2019-05-30 [0.1.4]
"""
import logging
import nw
from os import path
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor, QPixmap
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QDialogButtonBox, QLabel,
QPushButton, QHeaderView, QGridLayout, QGroupBox, QCheckBox
)
from nw.tools.optlaststate import OptLastState
from nw.constants import nwFiles
from nw.enum import nwItemClass
logger = logging.getLogger(__name__)
class GuiTimeLineView(QDialog):
def __init__(self, theParent, theProject, theIndex):
QDialog.__init__(self, theParent)
logger.debug("Initialising TimeLineView ...")
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.theIndex = theIndex
self.optState = TimeLineLastState(self.theProject,nwFiles.TLINE_OPT)
self.optState.loadSettings()
self.theMatrix = {}
self.numRows = 0
self.numCols = 0
self.outerBox = QVBoxLayout()
self.filterBox = QVBoxLayout()
self.centreBox = QHBoxLayout()
self.bottomBox = QHBoxLayout()
self.setWindowTitle("Timeline View")
self.setMinimumWidth(700)
self.setMinimumHeight(400)
winWidth = self.optState.validIntRange(self.optState.getSetting("winWidth"), 700, 10000, 700)
winHeight = self.optState.validIntRange(self.optState.getSetting("winHeight"), 400, 10000, 400)
self.resize(winWidth,winHeight)
# TimeLine Table
self.mainTable = QTableWidget()
self.mainTable.setGridStyle(Qt.NoPen)
self.hHeader = self.mainTable.horizontalHeader()
self.hHeader.setSectionResizeMode(QHeaderView.ResizeToContents)
self.mainTable.setHorizontalHeader(self.hHeader)
self.vHeader = self.mainTable.verticalHeader()
self.vHeader.setSectionResizeMode(QHeaderView.ResizeToContents)
self.mainTable.setVerticalHeader(self.vHeader)
# Option Box
self.optFilter = QGroupBox("Include Tags", self)
self.optFilterGrid = QGridLayout(self)
self.optFilter.setLayout(self.optFilterGrid)
self.filterPlot = QCheckBox("Plot tags", self)
self.filterPlot.setChecked(self.optState.getSetting("fPlot"))
self.filterPlot.stateChanged.connect(self._filterChange)
self.filterChar = QCheckBox("Character tags", self)
self.filterChar.setChecked(self.optState.getSetting("fChar"))
self.filterChar.stateChanged.connect(self._filterChange)
self.filterWorld = QCheckBox("Location tags", self)
self.filterWorld.setChecked(self.optState.getSetting("fWorld"))
self.filterWorld.stateChanged.connect(self._filterChange)
self.filterTime = QCheckBox("Timeline tags", self)
self.filterTime.setChecked(self.optState.getSetting("fTime"))
self.filterTime.stateChanged.connect(self._filterChange)
self.filterObject = QCheckBox("Object tags", self)
self.filterObject.setChecked(self.optState.getSetting("fObject"))
self.filterObject.stateChanged.connect(self._filterChange)
self.filterCustom = QCheckBox("Custom tags", self)
self.filterCustom.setChecked(self.optState.getSetting("fCustom"))
self.filterCustom.stateChanged.connect(self._filterChange)
self.optFilterGrid.addWidget(self.filterPlot, 0, 1)
self.optFilterGrid.addWidget(self.filterChar, 1, 1)
self.optFilterGrid.addWidget(self.filterWorld, 2, 1)
self.optFilterGrid.addWidget(self.filterTime, 3, 1)
self.optFilterGrid.addWidget(self.filterObject, 4, 1)
self.optFilterGrid.addWidget(self.filterCustom, 5, 1)
self.optHide = QGroupBox("Filters", self)
self.optHideGrid = QGridLayout(self)
self.optHide.setLayout(self.optHideGrid)
self.hideUnused = QCheckBox("Hide unused", self)
self.hideUnused.setChecked(self.optState.getSetting("hUnused"))
self.hideUnused.stateChanged.connect(self._filterChange)
self.optHideGrid.addWidget(self.hideUnused, 0, 1)
# Button Box
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.rejected.connect(self._doClose)
self.btnRebuild = QPushButton("Rebuild Index")
self.btnRebuild.clicked.connect(self.theParent.rebuildIndex)
self.btnRefresh = QPushButton("Refresh Table")
self.btnRefresh.clicked.connect(self._buildNovelList)
self.bottomBox.addWidget(self.btnRebuild)
self.bottomBox.addWidget(self.btnRefresh)
self.bottomBox.addStretch()
self.bottomBox.addWidget(self.buttonBox)
# Assemble
self.filterBox.addWidget(self.optFilter)
self.filterBox.addWidget(self.optHide)
self.filterBox.addStretch()
self.centreBox.addWidget(self.mainTable)
self.centreBox.addLayout(self.filterBox)
self.outerBox.addLayout(self.centreBox)
self.outerBox.addLayout(self.bottomBox)
self.setLayout(self.outerBox)
self._buildNovelList()
self.buttonBox.setFocus()
self.show()
logger.debug("TimeLineView initialisation complete")
return
def _buildNovelList(self):
self.mainTable.clear()
self.theIndex.buildNovelList()
self.numRows = len(self.theIndex.novelList)
self.mainTable.setRowCount(self.numRows)
theFilters = {}
theFilters["exClass"] = []
theFilters["hUnused"] = self.hideUnused.isChecked()
if not self.filterPlot.isChecked():
theFilters["exClass"].append(nwItemClass.PLOT)
if not self.filterChar.isChecked():
theFilters["exClass"].append(nwItemClass.CHARACTER)
if not self.filterWorld.isChecked():
theFilters["exClass"].append(nwItemClass.WORLD)
if not self.filterTime.isChecked():
theFilters["exClass"].append(nwItemClass.TIMELINE)
if not self.filterObject.isChecked():
theFilters["exClass"].append(nwItemClass.OBJECT)
if not self.filterCustom.isChecked():
theFilters["exClass"].append(nwItemClass.CUSTOM)
for n in range(len(self.theIndex.novelList)):
iDepth = self.theIndex.novelList[n][1]
iTitle = self.theIndex.novelList[n][2]
newItem = QTableWidgetItem("%s%s " % (" "*iDepth,iTitle))
self.mainTable.setVerticalHeaderItem(n, newItem)
theMap = self.theIndex.buildTagNovelMap(self.theIndex.tagIndex.keys(), theFilters)
self.numCols = len(theMap.keys())
self.mainTable.setColumnCount(self.numCols)
nCol = 0
for theTag, theCols in theMap.items():
newItem = QTableWidgetItem(" %s " % theTag)
self.mainTable.setHorizontalHeaderItem(nCol, newItem)
for n in range(len(theCols)):
if theCols[n] == 1:
pxNew = QPixmap(10,10)
pxNew.fill(QColor(0,120,0))
lblNew = QLabel()
lblNew.setPixmap(pxNew)
lblNew.setAlignment(Qt.AlignCenter)
lblNew.setAttribute(Qt.WA_TranslucentBackground)
self.mainTable.setCellWidget(n, nCol, lblNew)
elif theCols[n] == 2:
pxNew = QPixmap(10,10)
pxNew.fill(QColor(0,0,120))
lblNew = QLabel()
lblNew.setPixmap(pxNew)
lblNew.setAlignment(Qt.AlignCenter)
lblNew.setAttribute(Qt.WA_TranslucentBackground)
self.mainTable.setCellWidget(n, nCol, lblNew)
nCol += 1
return
def _doClose(self):
logger.verbose("GuiTimeLineView close button clicked")
winWidth = self.width()
winHeight = self.height()
fPlot = self.filterPlot.isChecked()
fChar = self.filterChar.isChecked()
fWorld = self.filterWorld.isChecked()
fTime = self.filterTime.isChecked()
fObject = self.filterObject.isChecked()
fCustom = self.filterCustom.isChecked()
hUnused = self.hideUnused.isChecked()
self.optState.setSetting("winWidth", winWidth)
self.optState.setSetting("winHeight",winHeight)
self.optState.setSetting("fPlot", fPlot)
self.optState.setSetting("fChar", fChar)
self.optState.setSetting("fWorld", fWorld)
self.optState.setSetting("fTime", fTime)
self.optState.setSetting("fObject", fObject)
self.optState.setSetting("fCustom", fCustom)
self.optState.setSetting("hUnused", hUnused)
self.optState.saveSettings()
self.close()
return
def _filterChange(self, checkState):
self._buildNovelList()
return
# END Class GuiTimeLineView
class TimeLineLastState(OptLastState):
def __init__(self, theProject, theFile):
OptLastState.__init__(self, theProject, theFile)
self.theState = {
"winWidth" : 700,
"winHeight" : 400,
"fPlot" : True,
"fChar" : True,
"fWorld" : True,
"fTime" : True,
"fObject" : True,
"fCustom" : True,
"hUnused" : True,
}
self.stringOpt = ()
self.boolOpt = ("fPlot","fChar","fWorld","fTime","fObject","fCustom","hUnused")
self.intOpt = ("winWidth","winHeight")
return
# END Class TimeLineLastState