Merge pull request #30 from vkbo/config_gui

Config GUI
This commit is contained in:
Veronica K. Berglyd Olsen
2019-06-11 20:58:20 +02:00
committed by GitHub
16 changed files with 759 additions and 61 deletions
+39 -31
View File
@@ -43,13 +43,14 @@ class Config:
self.appPath = None
self.appRoot = None
self.guiPath = None
self.themeRoot = None
self.themePath = None
# Set default values
self.confChanged = False
## General
self.guiTheme = "default"
self.guiTheme = "default_dark"
self.winGeometry = [1100, 650]
self.treeColWidth = [120, 30, 50]
self.mainPanePos = [300, 800]
@@ -63,10 +64,11 @@ class Config:
self.autoSaveDoc = 30
## Text Editor
self.textFont = None
self.textSize = 12
self.textFixedW = True
self.textWidth = 600
self.textMargin = [40, 40]
self.textSize = 13
self.tabWidth = 40
self.doJustify = True
self.autoSelect = True
@@ -77,9 +79,8 @@ class Config:
self.doReplaceDots = True
self.wordCountTimer = 5.0
self.fmtDoubleQuotes = ["",""]
self.fmtSingleQuotes = ["",""]
self.fmtApostrophe = ""
self.fmtDoubleQuotes = ["",""]
self.spellLanguage = "en_GB"
@@ -106,7 +107,8 @@ class Config:
self.appRoot = path.join(self.appPath,path.pardir)
self.helpPath = path.join(self.appRoot,"help","en_GB")
self.guiPath = path.join(self.appPath,"gui")
self.themePath = path.join(self.appPath,"themes")
self.themeRoot = path.join(self.appPath,"themes")
self.themePath = path.join(self.themeRoot)
# If config folder does not exist, make it.
# This assumes that the os config folder itself exists.
@@ -153,19 +155,22 @@ class Config:
## Editor
cnfSec = "Editor"
self.textFixedW = self._parseLine(cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW)
self.textWidth = self._parseLine(cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth)
self.textMargin = self._parseLine(cnfParse, cnfSec, "margins", self.CNF_LIST, self.textMargin)
self.textSize = self._parseLine(cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize)
self.tabWidth = self._parseLine(cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth)
self.doJustify = self._parseLine(cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify)
self.autoSelect = self._parseLine(cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect)
self.doReplace = self._parseLine(cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace)
self.doReplaceSQuote = self._parseLine(cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote)
self.doReplaceDQuote = self._parseLine(cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote)
self.doReplaceDash = self._parseLine(cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash)
self.doReplaceDots = self._parseLine(cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots)
self.spellLanguage = self._parseLine(cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage)
self.textFont = self._parseLine(cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont)
self.textSize = self._parseLine(cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize)
self.textFixedW = self._parseLine(cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW)
self.textWidth = self._parseLine(cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth)
self.textMargin = self._parseLine(cnfParse, cnfSec, "margins", self.CNF_LIST, self.textMargin)
self.tabWidth = self._parseLine(cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth)
self.doJustify = self._parseLine(cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify)
self.autoSelect = self._parseLine(cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect)
self.doReplace = self._parseLine(cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace)
self.doReplaceSQuote = self._parseLine(cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote)
self.doReplaceDQuote = self._parseLine(cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote)
self.doReplaceDash = self._parseLine(cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash)
self.doReplaceDots = self._parseLine(cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots)
self.fmtSingleQuotes = self._parseLine(cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes)
self.fmtDoubleQuotes = self._parseLine(cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes)
self.spellLanguage = self._parseLine(cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage)
## Path
cnfSec = "Path"
@@ -205,19 +210,22 @@ class Config:
## Editor
cnfSec = "Editor"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"fixedwidth", str(self.textFixedW))
cnfParse.set(cnfSec,"width", str(self.textWidth))
cnfParse.set(cnfSec,"margins", self._packList(self.textMargin))
cnfParse.set(cnfSec,"textsize", str(self.textSize))
cnfParse.set(cnfSec,"tabwidth", str(self.tabWidth))
cnfParse.set(cnfSec,"justify", str(self.doJustify))
cnfParse.set(cnfSec,"autoselect", str(self.autoSelect))
cnfParse.set(cnfSec,"autoreplace",str(self.doReplace))
cnfParse.set(cnfSec,"repsquotes", str(self.doReplaceSQuote))
cnfParse.set(cnfSec,"repdquotes", str(self.doReplaceDQuote))
cnfParse.set(cnfSec,"repdash", str(self.doReplaceDash))
cnfParse.set(cnfSec,"repdots", str(self.doReplaceDots))
cnfParse.set(cnfSec,"spellcheck", str(self.spellLanguage))
cnfParse.set(cnfSec,"textfont", str(self.textFont))
cnfParse.set(cnfSec,"textsize", str(self.textSize))
cnfParse.set(cnfSec,"fixedwidth", str(self.textFixedW))
cnfParse.set(cnfSec,"width", str(self.textWidth))
cnfParse.set(cnfSec,"margins", self._packList(self.textMargin))
cnfParse.set(cnfSec,"tabwidth", str(self.tabWidth))
cnfParse.set(cnfSec,"justify", str(self.doJustify))
cnfParse.set(cnfSec,"autoselect", str(self.autoSelect))
cnfParse.set(cnfSec,"autoreplace", str(self.doReplace))
cnfParse.set(cnfSec,"repsquotes", str(self.doReplaceSQuote))
cnfParse.set(cnfSec,"repdquotes", str(self.doReplaceDQuote))
cnfParse.set(cnfSec,"repdash", str(self.doReplaceDash))
cnfParse.set(cnfSec,"repdots", str(self.doReplaceDots))
cnfParse.set(cnfSec,"fmtsinglequote",self._packList(self.fmtSingleQuotes))
cnfParse.set(cnfSec,"fmtdoublequote",self._packList(self.fmtDoubleQuotes))
cnfParse.set(cnfSec,"spellcheck", str(self.spellLanguage))
## Path
cnfSec = "Path"
+50
View File
@@ -70,3 +70,53 @@ class nwLabels():
}
# END Class nwLabels
class nwQuotes():
"""Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark
"""
SYMBOLS = [
"\u0022", # Quotation mark
"\u0027", # Apostrophe
"\u00ab", # Left-pointing double angle quotation mark
"\u00bb", # Right-pointing double angle quotation mark
"\u2018", # Left single quotation mark
"\u2019", # Right single quotation mark
"\u201a", # Single low-9 quotation mark
"\u201b", # Single high-reversed-9 quotation mark
"\u201c", # Left double quotation mark
"\u201d", # Right double quotation mark
"\u201e", # Double low-9 quotation mark
"\u201f", # Double high-reversed-9 quotation mark
"\u2039", # Single left-pointing angle quotation mark
"\u203a", # Single right-pointing angle quotation mark
"\u2e42", # Double low-reversed-9 quotation mark
"\u300c", # Left corner bracket
"\u300d", # Right corner bracket
"\u300e", # Left white corner bracket
"\u300f", # Right white corner bracket
]
HTML = {
"\u0022" : """,
"\u0027" : "'",
"\u00ab" : "«",
"\u00bb" : "»",
"\u2018" : "‘",
"\u2019" : "’",
"\u201a" : "‚",
"\u201b" : "‛",
"\u201c" : "“",
"\u201d" : "”",
"\u201e" : "„",
"\u201f" : "‟",
"\u2039" : "‹",
"\u203a" : "›",
"\u2e42" : "⹂",
"\u300c" : "「",
"\u300d" : "」",
"\u300e" : "『",
"\u300f" : "』",
}
# END Class nwQuotes
+492
View File
@@ -0,0 +1,492 @@
# -*- 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 enchant
import nw
from os import path
from PyQt5.QtCore import Qt
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
)
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.appPath,"graphics","block.svg"))
self.svgGradient = QSvgWidget(self.gradPath)
self.svgGradient.setFixedWidth(80)
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)
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 ConfigEditor complete")
return
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.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.theParent.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.guiLookForm.addWidget(QLabel("Theme"), 0, 0)
self.guiLookForm.addWidget(self.guiLookTheme, 0, 1)
# Spell Checking
self.spellLang = QGroupBox("Spell Checker", self)
self.spellLangForm = QGridLayout(self)
self.spellLang.setLayout(self.spellLangForm)
self.spellLangList = QComboBox(self)
for spTag, spProvider in enchant.list_dicts():
self.spellLangList.addItem("%s [%s]" % (spTag, spProvider.name), 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(1, 1)
# AutoSave
self.autoSave = QGroupBox("Auto-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)
# Assemble
self.outerBox.addWidget(self.guiLook, 0, 0)
self.outerBox.addWidget(self.spellLang, 1, 0)
self.outerBox.addWidget(self.autoSave, 2, 0)
self.outerBox.setColumnStretch(2, 1)
self.outerBox.setRowStretch(3, 1)
self.setLayout(self.outerBox)
return
def saveValues(self):
validEntries = True
needsRestart = False
guiTheme = self.guiLookTheme.currentData()
spellLanguage = self.spellLangList.currentData()
autoSaveDoc = self.autoSaveDoc.value()
autoSaveProj = self.autoSaveProj.value()
# Check if restart is needed
needsRestart |= self.mainConf.guiTheme != guiTheme
self.mainConf.guiTheme = guiTheme
self.mainConf.spellLanguage = spellLanguage
self.mainConf.autoSaveDoc = autoSaveDoc
self.mainConf.autoSaveProj = autoSaveProj
self.mainConf.confChanged = True
return validEntries, needsRestart
# 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(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(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.testFlowAutoSelect = QCheckBox(self)
self.testFlowAutoSelect.setToolTip("Auto-select word under cursor when applying formatting.")
if self.mainConf.autoSelect:
self.testFlowAutoSelect.setCheckState(Qt.Checked)
else:
self.testFlowAutoSelect.setCheckState(Qt.Unchecked)
self.textFlowForm.addWidget(QLabel("Fixed Width"), 0, 0)
self.textFlowForm.addWidget(self.textFlowFixed, 0, 1)
self.textFlowForm.addWidget(self.textFlowWidth, 0, 2)
self.textFlowForm.addWidget(QLabel("px"), 0, 3)
self.textFlowForm.addWidget(QLabel("Justify Text"), 1, 0)
self.textFlowForm.addWidget(self.textFlowJustify, 1, 1)
self.textFlowForm.addWidget(QLabel("Auto-Select Text"), 2, 0)
self.textFlowForm.addWidget(self.testFlowAutoSelect, 2, 1)
self.textFlowForm.setColumnStretch(4, 1)
# Text Margins
self.textMargin = QGroupBox("Margins", self)
self.textMarginForm = QGridLayout(self)
self.textMargin.setLayout(self.textMarginForm)
self.textMarginHor = QSpinBox(self)
self.textMarginHor.setMinimum(0)
self.textMarginHor.setMaximum(2000)
self.textMarginHor.setSingleStep(1)
self.textMarginHor.setValue(self.mainConf.textMargin[0])
self.textMarginVer = QSpinBox(self)
self.textMarginVer.setMinimum(0)
self.textMarginVer.setMaximum(2000)
self.textMarginVer.setSingleStep(1)
self.textMarginVer.setValue(self.mainConf.textMargin[1])
self.textMarginTab = QSpinBox(self)
self.textMarginTab.setMinimum(0)
self.textMarginTab.setMaximum(200)
self.textMarginTab.setSingleStep(1)
self.textMarginTab.setValue(self.mainConf.tabWidth)
self.textMarginForm.addWidget(QLabel("Horizontal"), 0, 0)
self.textMarginForm.addWidget(self.textMarginHor, 0, 1)
self.textMarginForm.addWidget(QLabel("px"), 0, 2)
self.textMarginForm.addWidget(QLabel("Vertical"), 1, 0)
self.textMarginForm.addWidget(self.textMarginVer, 1, 1)
self.textMarginForm.addWidget(QLabel("px"), 1, 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)
# Auto-Replace
self.autoReplace = QGroupBox("Auto-Replace", self)
self.autoReplaceForm = QGridLayout(self)
self.autoReplace.setLayout(self.autoReplaceForm)
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.autoReplaceSStyleO = QLineEdit()
self.autoReplaceSStyleO.setMaxLength(1)
self.autoReplaceSStyleO.setFixedWidth(30)
self.autoReplaceSStyleO.setAlignment(Qt.AlignCenter)
self.autoReplaceSStyleO.setText(self.mainConf.fmtSingleQuotes[0])
self.autoReplaceSStyleC = QLineEdit()
self.autoReplaceSStyleC.setMaxLength(1)
self.autoReplaceSStyleC.setFixedWidth(30)
self.autoReplaceSStyleC.setAlignment(Qt.AlignCenter)
self.autoReplaceSStyleC.setText(self.mainConf.fmtSingleQuotes[1])
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.autoReplaceDStyleO = QLineEdit()
self.autoReplaceDStyleO.setMaxLength(1)
self.autoReplaceDStyleO.setFixedWidth(30)
self.autoReplaceDStyleO.setAlignment(Qt.AlignCenter)
self.autoReplaceDStyleO.setText(self.mainConf.fmtDoubleQuotes[0])
self.autoReplaceDStyleC = QLineEdit()
self.autoReplaceDStyleC.setMaxLength(1)
self.autoReplaceDStyleC.setFixedWidth(30)
self.autoReplaceDStyleC.setAlignment(Qt.AlignCenter)
self.autoReplaceDStyleC.setText(self.mainConf.fmtDoubleQuotes[1])
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("Enable Feature"), 0, 0)
self.autoReplaceForm.addWidget(self.autoReplaceMain, 0, 1)
self.autoReplaceForm.addWidget(QLabel("Single Quotes"), 1, 0)
self.autoReplaceForm.addWidget(self.autoReplaceSQ, 1, 1)
self.autoReplaceForm.addWidget(QLabel("Open"), 1, 2)
self.autoReplaceForm.addWidget(self.autoReplaceSStyleO, 1, 3)
self.autoReplaceForm.addWidget(QLabel("Close"), 1, 4)
self.autoReplaceForm.addWidget(self.autoReplaceSStyleC, 1, 5)
self.autoReplaceForm.addWidget(QLabel("Double Quotes"), 2, 0)
self.autoReplaceForm.addWidget(self.autoReplaceDQ, 2, 1)
self.autoReplaceForm.addWidget(QLabel("Open"), 2, 2)
self.autoReplaceForm.addWidget(self.autoReplaceDStyleO, 2, 3)
self.autoReplaceForm.addWidget(QLabel("Close"), 2, 4)
self.autoReplaceForm.addWidget(self.autoReplaceDStyleC, 2, 5)
self.autoReplaceForm.addWidget(QLabel("Hyphens with Dash"), 3, 0)
self.autoReplaceForm.addWidget(self.autoReplaceDash, 3, 1)
self.autoReplaceForm.addWidget(QLabel("Dots with Ellipsis"), 4, 0)
self.autoReplaceForm.addWidget(self.autoReplaceDots, 4, 1)
self.autoReplaceForm.setColumnStretch(6, 1)
# 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.autoReplace, 2, 0, 1, 2)
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()
autoSelect = self.testFlowAutoSelect.isChecked()
self.mainConf.textWidth = textWidth
self.mainConf.textFixedW = textFixedW
self.mainConf.doJustify = doJustify
self.mainConf.autoSelect = autoSelect
textMarginH = self.textMarginHor.value()
textMarginV = self.textMarginVer.value()
tabWidth = self.textMarginTab.value()
self.mainConf.textMargin[0] = textMarginH
self.mainConf.textMargin[1] = textMarginV
self.mainConf.tabWidth = tabWidth
doReplace = self.autoReplaceMain.isChecked()
doReplaceSQuote = self.autoReplaceSQ.isChecked()
doReplaceDQuote = self.autoReplaceDQ.isChecked()
doReplaceDash = self.autoReplaceDash.isChecked()
doReplaceDots = self.autoReplaceDash.isChecked()
fmtSingleQuotesO = self.autoReplaceSStyleO.text()
fmtSingleQuotesC = self.autoReplaceSStyleC.text()
fmtDoubleQuotesO = self.autoReplaceDStyleO.text()
fmtDoubleQuotesC = self.autoReplaceDStyleC.text()
self.mainConf.doReplace = doReplace
self.mainConf.doReplaceSQuote = doReplaceSQuote
self.mainConf.doReplaceDQuote = doReplaceDQuote
self.mainConf.doReplaceDash = doReplaceDash
self.mainConf.doReplaceDots = doReplaceDots
if self._checkQuoteSymbol(fmtSingleQuotesO):
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtSingleQuotesO, nwAlert.ERROR)
validEntries = False
if self._checkQuoteSymbol(fmtSingleQuotesC):
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtSingleQuotesC, nwAlert.ERROR)
validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesO):
self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO
else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtDoubleQuotesO, nwAlert.ERROR)
validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesC):
self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC
else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtDoubleQuotesC, nwAlert.ERROR)
validEntries = False
self.mainConf.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
+25 -2
View File
@@ -18,7 +18,7 @@ from time import time
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QTextEdit, QAction, QMenu, QShortcut
from PyQt5.QtGui import QTextCursor, QTextOption, QIcon, QKeySequence
from PyQt5.QtGui import QTextCursor, QTextOption, QIcon, QKeySequence, QFont
from nw.project.document import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter
@@ -55,7 +55,6 @@ class GuiDocEditor(QTextEdit):
self.typDQClose = self.mainConf.fmtDoubleQuotes[1]
self.typSQOpen = self.mainConf.fmtSingleQuotes[0]
self.typSQClose = self.mainConf.fmtSingleQuotes[1]
self.typApos = self.mainConf.fmtApostrophe
# Core Elements
self.theQDoc = self.document()
@@ -113,7 +112,21 @@ class GuiDocEditor(QTextEdit):
return True
def initEditor(self):
"""Initialise or re-initialise the editor with the user's settings.
This function is both called when the editor is created, and when the user changes the
main editor preferences.
"""
# Set Font
if self.mainConf.textFont is not None:
self.setFontFamily(self.mainConf.textFont)
else:
# If none is defined, set the default back to config
theFont = self.theQDoc.defaultFont().family()
self.mainConf.textFont = theFont
self.setFontPointSize(self.mainConf.textSize)
# Set text fixed width, or alternatively, just margins
if self.mainConf.textFixedW:
self.setLineWrapMode(QTextEdit.FixedPixelWidth)
self.setLineWrapColumnOrWidth(self.mainConf.textWidth)
@@ -121,12 +134,22 @@ class GuiDocEditor(QTextEdit):
mTB = self.mainConf.textMargin[0]
mLR = self.mainConf.textMargin[1]
self.setViewportMargins(mLR,mTB,mLR,mTB)
# Also set the document text options for the document text flow
theOpt = QTextOption()
if self.mainConf.tabWidth is not None:
theOpt.setTabStopDistance(self.mainConf.tabWidth)
if self.mainConf.doJustify:
theOpt.setAlignment(Qt.AlignJustify)
self.theQDoc.setDefaultTextOption(theOpt)
# If we have a document open, we should reload it in case the font changed
if self.theHandle is not None:
tHandle = self.theHandle
self.hLight.initHighlighter()
self.clearEditor()
self.loadText(tHandle)
return True
def loadText(self, tHandle):
+11 -3
View File
@@ -32,7 +32,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.theDict = None
self.theHandle = None
self.spellCheck = False
self.spellRx = None
self.hRules = []
self.hStyles = {}
self.colHead = QColor(*self.theTheme.colHead)
self.colHeadH = QColor(*self.theTheme.colHeadH)
@@ -47,6 +49,14 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colTagErr = QColor(*self.theTheme.colTagErr)
self.colRepTag = QColor(*self.theTheme.colRepTag)
self.initHighlighter()
logger.debug("DocHighlighter initialisation complete")
return
def initHighlighter(self):
self.hStyles = {
"header1" : self._makeFormat(self.colHead, "bold",1.8),
"header2" : self._makeFormat(self.colHead, "bold",1.6),
@@ -160,9 +170,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.rules = [(QRegularExpression(a),b) for (a,b) in self.hRules]
self.spellRx = QRegularExpression(r"\b[^\s]+\b")
logger.debug("DocHighlighter initialisation complete")
return
return True
##
# Setters
+6 -4
View File
@@ -492,10 +492,12 @@ class GuiMainMenu(QMenuBar):
menuItem.triggered.connect(self.theParent.rebuildIndex)
self.toolsMenu.addAction(menuItem)
# # Tools > Settings
# menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self)
# menuItem.setStatusTip("Preferences")
# self.toolsMenu.addAction(menuItem)
# Tools > Settings
menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self)
menuItem.setStatusTip("Preferences")
menuItem.setShortcut("Ctrl+,")
menuItem.triggered.connect(self.theParent.editConfigDialog)
self.toolsMenu.addAction(menuItem)
return
+20 -6
View File
@@ -19,7 +19,7 @@ from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QColor
from PyQt5.QtWidgets import (
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
QShortcut, QMessageBox, QProgressDialog
QShortcut, QMessageBox, QProgressDialog, QDialog
)
from nw.gui.doctree import GuiDocTree
@@ -27,6 +27,7 @@ from nw.gui.doceditor import GuiDocEditor
from nw.gui.docviewer import GuiDocViewer
from nw.gui.docdetails import GuiDocDetails
from nw.gui.mainmenu import GuiMainMenu
from nw.gui.configeditor import GuiConfigEditor
from nw.gui.projecteditor import GuiProjectEditor
from nw.gui.itemeditor import GuiItemEditor
from nw.gui.statusbar import GuiMainStatus
@@ -49,7 +50,7 @@ class GuiMain(QMainWindow):
logger.debug("Initialising GUI ...")
self.mainConf = nw.CONFIG
self.theTheme = Theme()
self.theTheme = Theme(self)
self.theProject = NWProject(self)
self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False
@@ -118,15 +119,11 @@ class GuiMain(QMainWindow):
# Set Up Autosaving Project Timer
self.asProjTimer = QTimer()
self.asProjTimer.setInterval(int(self.mainConf.autoSaveProj*1000))
self.asProjTimer.timeout.connect(self._autoSaveProject)
self.asProjTimer.start()
# Set Up Autosaving Document Timer
self.asDocTimer = QTimer()
self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000))
self.asDocTimer.timeout.connect(self._autoSaveDocument)
self.asDocTimer.start()
# Keyboard Shortcuts
QShortcut(Qt.Key_Return, self.treeView, context=Qt.WidgetShortcut, activated=self._treeKeyPressReturn)
@@ -138,6 +135,10 @@ class GuiMain(QMainWindow):
if self.mainConf.showGUI:
self.show()
self.initMain()
self.asProjTimer.start()
self.asDocTimer.start()
logger.debug("GUI initialisation complete")
return
@@ -149,6 +150,11 @@ class GuiMain(QMainWindow):
self.statusBar.clearStatus()
return True
def initMain(self):
self.asProjTimer.setInterval(int(self.mainConf.autoSaveProj*1000))
self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000))
return True
##
# Project Actions
##
@@ -454,6 +460,14 @@ class GuiMain(QMainWindow):
return projPath
return None
def editConfigDialog(self):
dlgConf = GuiConfigEditor(self, self.theProject)
if dlgConf.exec_() == QDialog.Accepted:
logger.debug("Applying new preferences")
self.initMain()
self.docEditor.initEditor()
return True
def editProjectDialog(self):
if self.hasProject:
dlgProj = GuiProjectEditor(self, self.theProject)
+12 -8
View File
@@ -373,8 +373,9 @@ class NWProject():
return True
def setSpellCheck(self, theMode):
self.spellCheck = theMode
self.setProjectChanged(True)
if self.spellCheck != theMode:
self.spellCheck = theMode
self.setProjectChanged(True)
return True
def setTreeOrder(self, newOrder):
@@ -385,18 +386,21 @@ class NWProject():
return True
def setLastEdited(self, tHandle):
self.lastEdited = tHandle
self.setProjectChanged(True)
if self.lastEdited != tHandle:
self.lastEdited = tHandle
self.setProjectChanged(True)
return True
def setLastViewed(self, tHandle):
self.lastViewed = tHandle
self.setProjectChanged(True)
if self.lastViewed != tHandle:
self.lastViewed = tHandle
self.setProjectChanged(True)
return True
def setProjectWordCount(self, theCount):
self.currWCount = theCount
self.setProjectChanged(True)
if self.currWCount != theCount:
self.currWCount = theCount
self.setProjectChanged(True)
return True
def getSessionWordCount(self):
+41 -2
View File
@@ -14,19 +14,30 @@ import logging
import configparser
import nw
from os import path
from os import path, listdir
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
class Theme:
def __init__(self):
def __init__(self, theParent):
self.mainConf = nw.CONFIG
self.theParent = theParent
self.cssName = "styles.css"
self.confName = "theme.conf"
self.themeList = []
# Loaded Theme Settings
## Main
self.themeName = "No Name"
## Syntax
self.colText = [0,0,0]
self.colLink = [0,0,0]
self.colHead = [0,0,0]
self.colHeadH = [0,0,0]
self.colEmph = [0,0,0]
@@ -83,6 +94,11 @@ class Theme:
logger.error("Could not load theme config file")
return False
## Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.themeName = confParser.get(cnfSec,"name")
## Syntax
cnfSec = "Syntax"
if confParser.has_section(cnfSec):
@@ -103,6 +119,29 @@ class Theme:
return True
def listThemes(self):
if len(self.themeList) > 0:
return self.themeList
confParser = configparser.ConfigParser()
for themeDir in listdir(self.mainConf.themeRoot):
themeConf = path.join(self.mainConf.themeRoot, themeDir, self.confName)
logger.verbose("Checking theme config for '%s'" % themeDir)
try:
confParser.read_file(open(themeConf))
except Exception as e:
self.theParent.makeAlert(["Could not load theme config file",str(e)],nwAlert.ERROR)
return []
themeName = ""
if confParser.has_section("Main"):
themeName = confParser.get("Main","name")
logger.verbose("Theme name is '%s'" % themeName)
if themeName != "":
self.themeList.append((themeDir, themeName))
return self.themeList
##
# Internal Functions
##
@@ -1,3 +1,7 @@
/**
* Default Theme: Dark
*/
* {
background-color: #999999;
}
@@ -1,3 +1,6 @@
[Main]
name = Default Dark
[Syntax]
text = 199, 207, 208
link = 184, 200, 0
+7
View File
@@ -0,0 +1,7 @@
/**
* Default Theme: Light
*/
QTreeView, QHeaderView {
font-size: 13px;
}
+18
View File
@@ -0,0 +1,18 @@
[Main]
name = Default Light
[Syntax]
text = 0, 0, 0
link = 0, 0, 0
headertext = 0, 0, 0
headertag = 0, 0, 0
emphasis = 0, 0, 0
straightquotes = 0, 0, 0
doublequotes = 0, 0, 0
singlequotes = 0, 0, 0
hidden = 50, 50, 50
keyword = 0, 0, 0
value = 0, 0, 0
spellcheckline = 0, 0, 0
tagerror = 0, 0, 0
replacetag = 0, 0, 0
+23
View File
@@ -187,3 +187,26 @@ Start: 2019-06-08 20:30:24 End: 2019-06-08 20:42:10 Words: 9
Start: 2019-06-08 20:42:14 End: 2019-06-08 20:42:37 Words: 0
Start: 2019-06-08 20:42:41 End: 2019-06-08 20:43:25 Words: 0
Start: 2019-06-08 20:43:29 End: 2019-06-08 20:43:42 Words: 0
Start: 2019-06-10 13:26:52 End: 2019-06-10 13:30:22 Words: 0
Start: 2019-06-10 13:30:28 End: 2019-06-10 13:31:44 Words: 0
Start: 2019-06-10 14:17:18 End: 2019-06-10 14:17:48 Words: 0
Start: 2019-06-10 14:20:17 End: 2019-06-10 14:21:11 Words: 0
Start: 2019-06-10 20:41:35 End: 2019-06-10 20:41:48 Words: 0
Start: 2019-06-10 20:42:15 End: 2019-06-10 20:42:27 Words: 0
Start: 2019-06-10 20:42:43 End: 2019-06-10 20:42:47 Words: 0
Start: 2019-06-10 20:45:02 End: 2019-06-10 20:45:07 Words: 0
Start: 2019-06-10 20:45:44 End: 2019-06-10 20:46:03 Words: 0
Start: 2019-06-10 20:46:08 End: 2019-06-10 20:47:15 Words: 0
Start: 2019-06-10 20:47:24 End: 2019-06-10 20:47:28 Words: 0
Start: 2019-06-10 20:47:46 End: 2019-06-10 20:47:59 Words: 0
Start: 2019-06-10 20:48:02 End: 2019-06-10 20:48:08 Words: 0
Start: 2019-06-10 20:57:47 End: 2019-06-10 20:58:01 Words: 0
Start: 2019-06-10 21:29:47 End: 2019-06-10 21:33:22 Words: 0
Start: 2019-06-10 21:33:26 End: 2019-06-10 21:34:59 Words: 0
Start: 2019-06-10 21:39:54 End: 2019-06-10 21:45:25 Words: 0
Start: 2019-06-10 21:45:29 End: 2019-06-10 21:47:40 Words: 0
Start: 2019-06-10 21:48:06 End: 2019-06-10 21:48:47 Words: 0
Start: 2019-06-10 21:48:51 End: 2019-06-10 21:49:07 Words: 0
Start: 2019-06-10 22:17:58 End: 2019-06-10 22:18:44 Words: 0
Start: 2019-06-10 22:46:35 End: 2019-06-10 22:49:34 Words: 0
Start: 2019-06-11 14:44:55 End: 2019-06-11 14:53:47 Words: 0
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-06-08 20:43:42">
<novelWriterXML appVersion="0.1.5" fileVersion="1.0" timeStamp="2019-06-11 14:46:19">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -80,7 +80,7 @@
<charCount>736</charCount>
<wordCount>137</wordCount>
<paraCount>6</paraCount>
<cursorPos>725</cursorPos>
<cursorPos>738</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>New File</name>
+6 -3
View File
@@ -1,6 +1,6 @@
[Main]
timestamp = 2019-05-25 23:33:23
theme = default
timestamp = 2019-06-11 18:39:59
theme = default_dark
[Sizes]
geometry = 1100, 650
@@ -14,10 +14,11 @@ autosaveproject = 60
autosavedoc = 30
[Editor]
textfont = None
textsize = 12
fixedwidth = True
width = 600
margins = 40, 40
textsize = 13
tabwidth = 40
justify = True
autoselect = True
@@ -26,6 +27,8 @@ repsquotes = True
repdquotes = True
repdash = True
repdots = True
fmtsinglequote = ,
fmtdoublequote = “, ”
spellcheck = en_GB
[Path]