Cleaned up the source files in the gui folder a bit.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user