diff --git a/nw/config.py b/nw/config.py index 94eb21bd..a12aaa9a 100644 --- a/nw/config.py +++ b/nw/config.py @@ -48,7 +48,8 @@ class Config: self.guiTheme = "default" self.winGeometry = [1100, 650] self.treeColWidth = [120, 30, 50] - self.mainPanePos = [300, 400, 400] + self.mainPanePos = [300, 800] + self.docPanePos = [400, 400] ## Project self.autoSaveProj = 60 @@ -125,6 +126,12 @@ class Config: # Get options + ## Main + cnfSec = "Main" + if confParser.has_section(cnfSec): + if confParser.has_option(cnfSec,"theme"): + self.guiTheme = confParser.get(cnfSec,"theme") + ## Sizes cnfSec = "Sizes" if confParser.has_section(cnfSec): @@ -138,7 +145,11 @@ class Config: ) if confParser.has_option(cnfSec,"mainpane"): self.mainPanePos = self.unpackList( - confParser.get(cnfSec,"mainpane"), 3, self.mainPanePos + confParser.get(cnfSec,"mainpane"), 2, self.mainPanePos + ) + if confParser.has_option(cnfSec,"docpane"): + self.docPanePos = self.unpackList( + confParser.get(cnfSec,"docpane"), 2, self.docPanePos ) ## Project @@ -199,6 +210,7 @@ class Config: cnfSec = "Main" confParser.add_section(cnfSec) confParser.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + confParser.set(cnfSec,"theme", str(self.guiTheme)) ## Sizes cnfSec = "Sizes" @@ -206,6 +218,7 @@ class Config: confParser.set(cnfSec,"geometry", self.packList(self.winGeometry)) confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth)) confParser.set(cnfSec,"mainpane", self.packList(self.mainPanePos)) + confParser.set(cnfSec,"docpane", self.packList(self.docPanePos)) ## Project cnfSec = "Project" @@ -298,4 +311,9 @@ class Config: self.confChanged = True return True + def setDocPanePos(self, panePos): + self.docPanePos = panePos + self.confChanged = True + return True + # End Class Config diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 11acf866..43aaa621 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -56,7 +56,7 @@ class GuiDocEditor(QTextEdit): # Core Elements self.theDoc = self.document() self.theDict = enchant.Dict(self.mainConf.spellLanguage) - self.hLight = GuiDocHighlighter(self.theDoc) + self.hLight = GuiDocHighlighter(self.theDoc, self.theParent.theTheme) self.hLight.setDict(self.theDict) # Context Menu diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 30787793..4db6267a 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -20,27 +20,27 @@ logger = logging.getLogger(__name__) class GuiDocHighlighter(QSyntaxHighlighter): - def __init__(self, theDoc): + def __init__(self, theDoc, theTheme): QSyntaxHighlighter.__init__(self, theDoc) logger.debug("Initialising DocHighlighter ...") self.mainConf = nw.CONFIG self.theDoc = theDoc + self.theTheme = theTheme self.theDict = None self.spellCheck = False self.hRules = [] - self.colHead = QColor( 0,155,200) - self.colHeadH = QColor( 0,105,135) - self.colEmph = QColor(200,120, 0) - self.colDialN = QColor(200, 46, 0) - self.colDialD = QColor(184,200, 0) - self.colDialS = QColor(136,200, 0) - self.colComm = QColor(150,150,150) - self.colKey = QColor(200, 46, 0) - self.colVal = QColor(184,200, 0) - - self.colSpell = QColor(200, 46, 0) + self.colHead = QColor(*self.theTheme.colHead) + self.colHeadH = QColor(*self.theTheme.colHeadH) + self.colEmph = QColor(*self.theTheme.colEmph) + self.colDialN = QColor(*self.theTheme.colDialN) + self.colDialD = QColor(*self.theTheme.colDialD) + self.colDialS = QColor(*self.theTheme.colDialS) + self.colComm = QColor(*self.theTheme.colComm) + self.colKey = QColor(*self.theTheme.colKey) + self.colVal = QColor(*self.theTheme.colVal) + self.colSpell = QColor(*self.theTheme.colSpell) self.hStyles = { "header1" : self._makeFormat(self.colHead, "bold",1.8), diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index b6f3d666..f179d140 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -27,9 +27,16 @@ class GuiDocViewer(QTextBrowser): # Class Variables self.mainConf = nw.CONFIG self.theParent = theParent + self.theTheme = theParent.theTheme self.theDoc = self.document() - + self.theDoc.setDefaultStyleSheet(( + "h1, h2, h3, h4 {{" + " color: rgb({0},{1},{2});" + "}}" + ).format( + *self.theTheme.colHead + )) self.theDoc.setDocumentMargin(self.mainConf.textMargin[0]) self.setMinimumWidth(300) diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 36e1ecad..6bcea030 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -18,6 +18,7 @@ from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSpl from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtCore import Qt, QTimer +from nw.theme import Theme from nw.gui.doctree import GuiDocTree from nw.gui.doceditor import GuiDocEditor from nw.gui.docviewer import GuiDocViewer @@ -42,12 +43,14 @@ class GuiMain(QMainWindow): logger.debug("Initialising GUI ...") self.mainConf = nw.CONFIG + self.theTheme = Theme() self.theProject = NWProject(self) self.theDocument = NWDoc(self.theProject, self) self.resize(*self.mainConf.winGeometry) self._setWindowTitle() self.setWindowIcon(QIcon(path.join(self.mainConf.appPath,"..","novelWriter.svg"))) + self.theTheme.loadTheme() # Main GUI Elements self.docEditor = GuiDocEditor(self) @@ -70,27 +73,29 @@ class GuiMain(QMainWindow): self.treeBox.addWidget(self.docDetails) self.treePane.setLayout(self.treeBox) + self.splitView = QSplitter(Qt.Horizontal) + self.splitView.addWidget(self.docEditor) + self.splitView.addWidget(self.docViewer) + self.splitMain = QSplitter(Qt.Horizontal) self.splitMain.addWidget(self.treePane) - self.splitMain.addWidget(self.docEditor) - self.splitMain.addWidget(self.docViewer) + self.splitMain.addWidget(self.splitView) self.splitMain.setSizes(self.mainConf.mainPanePos) self.splitMain.splitterMoved.connect(self._splitMainMove) self.setCentralWidget(self.splitMain) self.idxTree = self.splitMain.indexOf(self.treePane) - self.idxEditor = self.splitMain.indexOf(self.docEditor) - self.idxViewer = self.splitMain.indexOf(self.docViewer) + self.idxMain = self.splitMain.indexOf(self.splitView) + self.idxEditor = self.splitView.indexOf(self.docEditor) + self.idxViewer = self.splitView.indexOf(self.docViewer) self.splitMain.setCollapsible(self.idxTree, False) - self.splitMain.setCollapsible(self.idxEditor, False) - self.splitMain.setCollapsible(self.idxViewer, True) + self.splitMain.setCollapsible(self.idxMain, False) + self.splitView.setCollapsible(self.idxEditor, False) + self.splitView.setCollapsible(self.idxViewer, True) self.docViewer.setVisible(False) - pPos = self.mainConf.mainPanePos - tPos = [pPos[0], pPos[1]+pPos[2]] - self.splitMain.setSizes(tPos) # Build GUI Elements self.treeView.itemSelectionChanged.connect(self._treeSingleClick) @@ -105,11 +110,7 @@ class GuiMain(QMainWindow): self.statusBar.showMessage("Ready") # Load Theme StyleSheet - cssFile = path.join(self.mainConf.themePath,self.mainConf.guiTheme+".css") - if path.isfile(cssFile): - with open(cssFile,mode="r") as inFile: - theCss = inFile.read() - self.setStyleSheet(theCss) + self.setStyleSheet(self.theTheme.cssData) self.asProjTimer = QTimer() self.asProjTimer.setInterval(int(self.mainConf.autoSaveProj*1000)) @@ -260,11 +261,10 @@ class GuiMain(QMainWindow): bPos = self.splitMain.sizes() self.docViewer.setVisible(True) - if bPos[2] == 0: - bWidth = bPos[1]+bPos[2] - bPos[1] = int(bWidth/2) - bPos[2] = bWidth-bPos[1] - self.splitMain.setSizes(bPos) + vPos = [0,0] + vPos[0] = int(bPos[1]/2) + vPos[1] = bPos[1]-vPos[0] + self.splitView.setSizes(vPos) self.docEditor.changeWidth() return True @@ -346,6 +346,7 @@ class GuiMain(QMainWindow): self.mainConf.setWinSize(self.width(), self.height()) self.mainConf.setTreeColWidths(self.treeView.getColumnSizes()) self.mainConf.setMainPanePos(self.splitMain.sizes()) + self.mainConf.setDocPanePos(self.splitView.sizes()) self.mainConf.saveConfig() return diff --git a/nw/theme.py b/nw/theme.py new file mode 100644 index 00000000..cab4c302 --- /dev/null +++ b/nw/theme.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +"""novelWriter Theme Class + + novelWriter – Theme Class +=========================== + This class reads and store the main theme + + File History: + Created: 2019-05-18 [0.1.3] + +""" + +import logging +import configparser +import nw + +from os import path + +logger = logging.getLogger(__name__) + +class Theme: + + def __init__(self): + + self.mainConf = nw.CONFIG + self.cssName = "styles.css" + self.confName = "theme.conf" + + # Loaded Theme Settings + self.colHead = [0,0,0] + self.colHeadH = [0,0,0] + self.colEmph = [0,0,0] + self.colDialN = [0,0,0] + self.colDialD = [0,0,0] + self.colDialS = [0,0,0] + self.colComm = [0,0,0] + self.colKey = [0,0,0] + self.colVal = [0,0,0] + self.colSpell = [0,0,0] + + # Changeable Settings + self.guiTheme = None + self.themePath = None + self.confFile = None + self.cssFile = None + self.cssData = "" + + self.updateTheme() + + return + + ## + # Actions + ## + + def updateTheme(self): + self.guiTheme = self.mainConf.guiTheme + self.themePath = path.join(self.mainConf.themePath, self.guiTheme) + self.confFile = path.join(self.themePath,self.confName) + self.cssFile = path.join(self.themePath,self.cssName) + return True + + def loadTheme(self): + + logger.debug("Loading theme files") + + # CSS File + try: + if path.isfile(self.cssFile): + with open(self.cssFile,mode="r") as inFile: + self.cssData = inFile.read() + except Exception as e: + logger.error("Could not load theme css file") + return False + + # Color Config + confParser = configparser.ConfigParser() + try: + confParser.read_file(open(self.confFile)) + except Exception as e: + logger.error("Could not load theme config file") + return False + + ## Syntax + cnfSec = "Syntax" + if confParser.has_section(cnfSec): + self.colHead = self._loadColour(confParser,cnfSec,"headertext") + self.colHeadH = self._loadColour(confParser,cnfSec,"headertag") + self.colEmph = self._loadColour(confParser,cnfSec,"emphasis") + self.colDialN = self._loadColour(confParser,cnfSec,"straightquotes") + self.colDialD = self._loadColour(confParser,cnfSec,"doublequotes") + self.colDialS = self._loadColour(confParser,cnfSec,"singlequotes") + self.colComm = self._loadColour(confParser,cnfSec,"hidden") + self.colKey = self._loadColour(confParser,cnfSec,"keyword") + self.colVal = self._loadColour(confParser,cnfSec,"value") + self.colSpell = self._loadColour(confParser,cnfSec,"spellcheckline") + + return True + + ## + # Internal Functions + ## + + def _loadColour(self, confParser, cnfSec, cnfName): + if confParser.has_option(cnfSec,cnfName): + inData = confParser.get(cnfSec,cnfName).split(",") + outData = [] + try: + outData.append(int(inData[0])) + outData.append(int(inData[1])) + outData.append(int(inData[2])) + except: + logger.error("Could not load theme colours for '%s' from config file" % cnfName) + outData = [0,0,0] + else: + logger.warning("Could not find theme colours for '%s' in config file" % cnfName) + outData = [0,0,0] + return outData + +# End Class Theme diff --git a/nw/themes/default.css b/nw/themes/default/styles.css similarity index 100% rename from nw/themes/default.css rename to nw/themes/default/styles.css diff --git a/nw/themes/default/theme.conf b/nw/themes/default/theme.conf new file mode 100644 index 00000000..117f53ea --- /dev/null +++ b/nw/themes/default/theme.conf @@ -0,0 +1,11 @@ +[Syntax] +headertext = 0, 155, 200 +headertag = 0, 105, 135 +emphasis = 200, 120, 0 +straightquotes = 200, 46, 0 +doublequotes = 184, 200, 0 +singlequotes = 136, 200, 0 +hidden = 150, 150, 150 +keyword = 200, 46, 0 +value = 184, 200, 0 +spellcheckline = 200, 46, 0 diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 0e68ae5c..03750f32 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -8,8 +8,8 @@ False - 96b68994dfa3d - 96b68994dfa3d + 636b6aa9b697b + 636b6aa9b697b diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index f11f5904..112f9fd8 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -1,10 +1,12 @@ [Main] timestamp = 2019-05-18 15:06:44 +theme = default [Sizes] geometry = 1100, 650 treecols = 120, 30, 50 -mainpane = 300, 400, 400 +mainpane = 300, 800 +docpane = 400, 400 [Project] autosaveproject = 60 diff --git a/tests/test_config.py b/tests/test_config.py index a46c3c41..12fc5c18 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -70,7 +70,8 @@ def testConfigSetMainPanePos(nwTemp,nwRef): refConf = path.join(nwRef, "novelwriter.conf") assert theConf.setMainPanePos([0, 0]) assert theConf.confChanged - assert theConf.setMainPanePos([300, 400, 400]) + assert theConf.setMainPanePos([300, 800]) + assert theConf.setDocPanePos([400, 400]) assert theConf.saveConfig() assert cmpFiles(tmpConf, refConf, [2]) assert not theConf.confChanged