Merge pull request #61 from vkbo/timeline_update

Timeline Update
This commit is contained in:
Veronica K. Berglyd Olsen
2019-10-21 21:07:51 +02:00
committed by GitHub
11 changed files with 248 additions and 94 deletions
-14
View File
@@ -65,9 +65,6 @@ class Config:
self.mainPanePos = [300, 800]
self.docPanePos = [400, 400]
## Dialogs
self.dlgTimeLine = [600, 400]
## Project
self.autoSaveProj = 60
self.autoSaveDoc = 30
@@ -182,7 +179,6 @@ class Config:
self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth)
self.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos)
self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos)
self.dlgTimeLine = self._parseLine(cnfParse, cnfSec, "timeline", self.CNF_LIST, self.dlgTimeLine)
## Project
cnfSec = "Project"
@@ -245,7 +241,6 @@ class Config:
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
cnfParse.set(cnfSec,"timeline", self._packList(self.dlgTimeLine))
## Project
cnfSec = "Project"
@@ -326,15 +321,6 @@ class Config:
self.confChanged = True
return True
def setTLineSize(self, newWidth, newHeight):
if abs(self.dlgTimeLine[0] - newWidth) > 5:
self.dlgTimeLine[0] = newWidth
self.confChanged = True
if abs(self.dlgTimeLine[1] - newHeight) > 5:
self.dlgTimeLine[1] = newHeight
self.confChanged = True
return True
def setTreeColWidths(self, colWidths):
self.treeColWidth = colWidths
self.confChanged = True
+1
View File
@@ -26,6 +26,7 @@ class nwFiles():
SESS_INFO = "sessionInfo.log"
INDEX_FILE = "tagsIndex.json"
EXPORT_OPT = "exportOptions.json"
TLINE_OPT = "timelineOptions.json"
# END Class nwFiles
+6 -52
View File
@@ -12,7 +12,6 @@
import logging
import time
import json
import nw
from os import path
@@ -26,10 +25,10 @@ from PyQt5.QtWidgets import (
from nw.project.document import NWDoc
from nw.tools.translate import numberToWord
from nw.tools.optlaststate import OptLastState
from nw.convert.textfile import TextFile
from nw.convert.htmlfile import HtmlFile
from nw.convert.markdownfile import MarkdownFile
from nw.common import checkString, checkBool, checkInt
from nw.constants import nwFiles
from nw.enum import nwItemType
@@ -45,7 +44,8 @@ class GuiExport(QDialog):
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.optState = ExportLastState(self.theProject)
self.optState = ExportLastState(self.theProject,nwFiles.EXPORT_OPT)
self.optState.loadSettings()
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
@@ -443,10 +443,10 @@ class GuiExportMain(QWidget):
# END Class GuiExportMain
class ExportLastState():
class ExportLastState(OptLastState):
def __init__(self, theProject):
self.theProject = theProject
def __init__(self, theProject, theFile):
OptLastState.__init__(self, theProject, theFile)
self.theState = {
"wNovel" : True,
"wNotes" : False,
@@ -462,52 +462,6 @@ class ExportLastState():
self.stringOpt = ("chFormat","unFormat","scFormat","seFormat","saveTo")
self.boolOpt = ("wNovel","wNotes","wComments")
self.intOpt = ("eFormat","fixWidth")
self.loadSettings()
return
def loadSettings(self):
stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT)
theState = {}
if path.isfile(stateFile):
logger.debug("Loading export options file")
try:
with open(stateFile,mode="r") as inFile:
theJson = inFile.read()
theState = json.loads(theJson)
except Exception as e:
logger.error("Failed to load export options file")
logger.error(str(e))
return False
for anOpt in theState:
self.theState[anOpt] = theState[anOpt]
return True
def saveSettings(self):
stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT)
logger.debug("Saving export options file")
try:
with open(stateFile,mode="w+") as outFile:
outFile.write(json.dumps(self.theState, indent=2))
except Exception as e:
logger.error("Failed to save export options file")
logger.error(str(e))
return False
return True
def setSetting(self, setName, setValue):
if setName in self.theState:
self.theState[setName] = setValue
else:
return False
return True
def getSetting(self, setName):
if setName in self.stringOpt:
return checkString(self.theState[setName],self.theState[setName],False)
elif setName in self.boolOpt:
return checkBool(self.theState[setName],self.theState[setName],False)
elif setName in self.intOpt:
return checkInt(self.theState[setName],self.theState[setName],False)
return None
# END Class ExportLastState
+138 -16
View File
@@ -13,13 +13,18 @@
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
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):
@@ -33,16 +38,27 @@ class GuiTimeLineView(QDialog):
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.setMinimumSize(*self.mainConf.dlgTimeLine)
self.setMinimumWidth(700)
self.setMinimumHeight(400)
self.resize(
self.optState.getSetting("winWidth"),
self.optState.getSetting("winHeight")
)
# TimeLine Table
self.mainTable = QTableWidget()
self.mainTable.setGridStyle(Qt.NoPen)
@@ -54,6 +70,41 @@ class GuiTimeLineView(QDialog):
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.filterChar = QCheckBox("Character tags", self)
self.filterChar.setChecked(self.optState.getSetting("fChar"))
self.filterWorld = QCheckBox("Location tags", self)
self.filterWorld.setChecked(self.optState.getSetting("fWorld"))
self.filterTime = QCheckBox("Timeline tags", self)
self.filterTime.setChecked(self.optState.getSetting("fTime"))
self.filterObject = QCheckBox("Object tags", self)
self.filterObject.setChecked(self.optState.getSetting("fObject"))
self.filterCustom = QCheckBox("Custom tags", self)
self.filterCustom.setChecked(self.optState.getSetting("fCustom"))
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.optHideGrid.addWidget(self.hideUnused, 0, 1)
# Button Box
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.rejected.connect(self._doClose)
@@ -63,14 +114,21 @@ class GuiTimeLineView(QDialog):
self.btnRefresh = QPushButton("Refresh Table")
self.btnRefresh.clicked.connect(self._buildNovelList)
self.setLayout(self.outerBox)
self.outerBox.addWidget(self.mainTable)
self.outerBox.addLayout(self.bottomBox)
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()
@@ -82,13 +140,28 @@ class GuiTimeLineView(QDialog):
def _buildNovelList(self):
self.theIndex.buildNovelList()
self.numRows = len(self.theIndex.novelList)
self.numCols = len(self.theIndex.tagIndex.keys())
self.mainTable.clear()
self.theIndex.buildNovelList()
self.numRows = len(self.theIndex.novelList)
self.mainTable.setRowCount(self.numRows)
self.mainTable.setColumnCount(self.numCols)
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]
@@ -96,14 +169,17 @@ class GuiTimeLineView(QDialog):
newItem = QTableWidgetItem("%s%s " % (" "*iDepth,iTitle))
self.mainTable.setVerticalHeaderItem(n, newItem)
theMap = self.theIndex.buildTagNovelMap(self.theIndex.tagIndex.keys())
nCol = 0
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 = QPixmap(10,10)
pxNew.fill(QColor(0,120,0))
lblNew = QLabel()
lblNew.setPixmap(pxNew)
@@ -111,7 +187,7 @@ class GuiTimeLineView(QDialog):
lblNew.setAttribute(Qt.WA_TranslucentBackground)
self.mainTable.setCellWidget(n, nCol, lblNew)
elif theCols[n] == 2:
pxNew = QPixmap(10,10)
pxNew = QPixmap(10,10)
pxNew.fill(QColor(0,0,120))
lblNew = QLabel()
lblNew.setPixmap(pxNew)
@@ -123,8 +199,54 @@ class GuiTimeLineView(QDialog):
return
def _doClose(self):
self.mainConf.setTLineSize(self.width(), self.height())
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
# 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
+5 -5
View File
@@ -54,11 +54,11 @@ class GuiMain(QMainWindow):
logger.info("Starting %s" % nw.__package__)
logger.debug("Initialising GUI ...")
self.mainConf = nw.CONFIG
self.theTheme = Theme(self)
self.theProject = NWProject(self)
self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False
self.mainConf = nw.CONFIG
self.theTheme = Theme(self)
self.theProject = NWProject(self)
self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False
logger.info("Qt5 Version: %s (%d)" % (self.mainConf.verQtString, self.mainConf.verQtValue))
logger.info("PyQt5 Version: %s (%d)" % (self.mainConf.verPyQtString, self.mainConf.verPyQtValue))
+15 -2
View File
@@ -319,18 +319,24 @@ class NWIndex():
return True
def buildTagNovelMap(self, theTags):
def buildTagNovelMap(self, theTags, theFilters=None):
tagMap = {}
tagClass = {}
exClass = []
if theFilters is not None:
if "exClass" in theFilters.keys():
exClass = theFilters["exClass"]
for theTag in theTags:
tagMap[theTag] = [0]*len(self.novelOrder)
try:
tagClass[theTag] = nwItemClass[self.tagIndex[theTag][2]]
except:
logger.error("Could not map '%s' to nwItemClass" % self.tagIndex[theTag][2])
tagClass[theTag] = None
if tagClass[theTag] not in exClass:
tagMap[theTag] = [0]*len(self.novelOrder)
for tHandle in self.refIndex:
for nLine, tKey, tTag, nTitle in self.refIndex[tHandle]:
@@ -342,6 +348,13 @@ class NWIndex():
except:
logger.error("Could not find '%s:%d' in novelOrder" % (tHandle, nTitle))
if theFilters["hUnused"]:
tagMapFiltered = {}
for theTag in tagMap.keys():
if sum(tagMap[theTag]) > 0:
tagMapFiltered[theTag] = tagMap[theTag]
return tagMapFiltered
return tagMap
# END Class NWIndex
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""novelWriter Options Last State
novelWriter Options Last State
==================================
Class holding the last state of GUI options
File History:
Created: 2019-10-21 [0.3.1]
"""
import logging
import json
import nw
from os import path
from nw.common import checkString, checkBool, checkInt
logger = logging.getLogger(__name__)
class OptLastState():
def __init__(self, theProject, theFile):
self.theProject = theProject
self.theFile = theFile
self.theState = {}
self.stringOpt = ()
self.boolOpt = ()
self.intOpt = ()
return
def loadSettings(self):
stateFile = path.join(self.theProject.projMeta,self.theFile)
theState = {}
if path.isfile(stateFile):
logger.debug("Loading options file")
try:
with open(stateFile,mode="r") as inFile:
theJson = inFile.read()
theState = json.loads(theJson)
except Exception as e:
logger.error("Failed to load options file")
logger.error(str(e))
return False
for anOpt in theState:
self.theState[anOpt] = theState[anOpt]
return True
def saveSettings(self):
stateFile = path.join(self.theProject.projMeta,self.theFile)
logger.debug("Saving options file")
try:
with open(stateFile,mode="w+") as outFile:
outFile.write(json.dumps(self.theState, indent=2))
except Exception as e:
logger.error("Failed to save options file")
logger.error(str(e))
return False
return True
def setSetting(self, setName, setValue):
if setName in self.theState:
self.theState[setName] = setValue
else:
return False
return True
def getSetting(self, setName):
if setName in self.stringOpt:
return checkString(self.theState[setName],self.theState[setName],False)
elif setName in self.boolOpt:
return checkBool(self.theState[setName],self.theState[setName],False)
elif setName in self.intOpt:
return checkInt(self.theState[setName],self.theState[setName],False)
return None
# END Class OptLastState
@@ -3,7 +3,7 @@
% Begin Meta
@pov: Jane
@char: John
@location: Earth
@location: Space
% End Meta
Some text here would look good as well, and maybe some "dialogue"?
@@ -1,7 +1,7 @@
### Another Scene
@pov: John
@location: Space
@location: Earth
This is the second scene in out story. We have no idea whats going on, so were just going to ramble on until we have a few lines of text so that the editor has something to work with.
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.2.3" fileVersion="1.0" timeStamp="2019-10-19 13:37:25">
<novelWriterXML appVersion="0.3.1" fileVersion="1.0" timeStamp="2019-10-21 20:54:53">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -82,7 +82,7 @@
<charCount>713</charCount>
<wordCount>132</wordCount>
<paraCount>6</paraCount>
<cursorPos>85</cursorPos>
<cursorPos>72</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
-1
View File
@@ -8,7 +8,6 @@ geometry = 1100, 650
treecols = 120, 30, 50
mainpane = 300, 800
docpane = 400, 400
timeline = 600, 400
[Project]
autosaveproject = 60