Merge pull request #140 from vkbo/project_outline
Synopsis and Outline Feature
This commit is contained in:
@@ -17,3 +17,4 @@ value = 184, 200, 0
|
|||||||
spellcheckline = 200, 46, 0
|
spellcheckline = 200, 46, 0
|
||||||
tagerror = 46, 200, 0
|
tagerror = 46, 200, 0
|
||||||
replacetag = 0, 184, 46
|
replacetag = 0, 184, 46
|
||||||
|
modifier = 200, 120, 0
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ value = 50, 150, 50
|
|||||||
spellcheckline = 200, 0, 0
|
spellcheckline = 200, 0, 0
|
||||||
tagerror = 0, 150, 0
|
tagerror = 0, 150, 0
|
||||||
replacetag = 0, 150, 0
|
replacetag = 0, 150, 0
|
||||||
|
modifier = 150, 110, 30
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 150, 74, 193
|
|||||||
spellcheckline = 222, 61, 58
|
spellcheckline = 222, 61, 58
|
||||||
tagerror = 8, 145, 106
|
tagerror = 8, 145, 106
|
||||||
replacetag = 42, 162, 152
|
replacetag = 42, 162, 152
|
||||||
|
modifier = 224, 175, 5
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 199, 146, 234
|
|||||||
spellcheckline = 247, 140, 108
|
spellcheckline = 247, 140, 108
|
||||||
tagerror = 173, 219, 103
|
tagerror = 173, 219, 103
|
||||||
replacetag = 127, 219, 202
|
replacetag = 127, 219, 202
|
||||||
|
modifier = 236, 196, 141
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 137, 89, 168
|
|||||||
spellcheckline = 240, 40, 41
|
spellcheckline = 240, 40, 41
|
||||||
tagerror = 113, 140, 0
|
tagerror = 113, 140, 0
|
||||||
replacetag = 62, 153, 159
|
replacetag = 62, 153, 159
|
||||||
|
modifier = 245, 135, 31
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 178, 148, 187
|
|||||||
spellcheckline = 204, 102, 102
|
spellcheckline = 204, 102, 102
|
||||||
tagerror = 181, 189, 104
|
tagerror = 181, 189, 104
|
||||||
replacetag = 138, 190, 183
|
replacetag = 138, 190, 183
|
||||||
|
modifier = 222, 147, 95
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 235, 187, 255
|
|||||||
spellcheckline = 255, 157, 164
|
spellcheckline = 255, 157, 164
|
||||||
tagerror = 209, 241, 169
|
tagerror = 209, 241, 169
|
||||||
replacetag = 153, 255, 255
|
replacetag = 153, 255, 255
|
||||||
|
modifier = 255, 197, 143
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 195, 151, 216
|
|||||||
spellcheckline = 213, 78, 83
|
spellcheckline = 213, 78, 83
|
||||||
tagerror = 185, 202, 74
|
tagerror = 185, 202, 74
|
||||||
replacetag = 112, 192, 177
|
replacetag = 112, 192, 177
|
||||||
|
modifier = 231, 140, 69
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ value = 204, 153, 204
|
|||||||
spellcheckline = 242, 119, 122
|
spellcheckline = 242, 119, 122
|
||||||
tagerror = 153, 204, 153
|
tagerror = 153, 204, 153
|
||||||
replacetag = 102, 204, 204
|
replacetag = 102, 204, 204
|
||||||
|
modifier = 249, 145, 57
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from nw.constants.constants import (
|
|||||||
nwConst, nwFiles, nwKeyWords, nwLabels, nwDependencies, nwQuotes, nwUnicode
|
nwConst, nwFiles, nwKeyWords, nwLabels, nwDependencies, nwQuotes, nwUnicode
|
||||||
)
|
)
|
||||||
from nw.constants.enum import (
|
from nw.constants.enum import (
|
||||||
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType
|
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -22,4 +22,5 @@ __all__ = [
|
|||||||
"nwItemClass",
|
"nwItemClass",
|
||||||
"nwItemLayout",
|
"nwItemLayout",
|
||||||
"nwItemType",
|
"nwItemType",
|
||||||
]
|
"nwOutline",
|
||||||
|
]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nw.constants.enum import nwItemClass, nwItemLayout
|
from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline
|
||||||
|
|
||||||
class nwConst():
|
class nwConst():
|
||||||
|
|
||||||
@@ -104,6 +104,24 @@ class nwLabels():
|
|||||||
nwKeyWords.ENTITY_KEY : "Entities",
|
nwKeyWords.ENTITY_KEY : "Entities",
|
||||||
nwKeyWords.CUSTOM_KEY : "Custom",
|
nwKeyWords.CUSTOM_KEY : "Custom",
|
||||||
}
|
}
|
||||||
|
OUTLINE_COLS = {
|
||||||
|
nwOutline.TITLE : "Title",
|
||||||
|
nwOutline.LEVEL : "Level",
|
||||||
|
nwOutline.LABEL : "Document",
|
||||||
|
nwOutline.LINE : "Line",
|
||||||
|
nwOutline.CCOUNT : "Chars",
|
||||||
|
nwOutline.WCOUNT : "Words",
|
||||||
|
nwOutline.PCOUNT : "Pars",
|
||||||
|
nwOutline.POV : "POV",
|
||||||
|
nwOutline.CHAR : KEY_NAME[nwKeyWords.CHAR_KEY],
|
||||||
|
nwOutline.PLOT : KEY_NAME[nwKeyWords.PLOT_KEY],
|
||||||
|
nwOutline.TIME : KEY_NAME[nwKeyWords.TIME_KEY],
|
||||||
|
nwOutline.WORLD : KEY_NAME[nwKeyWords.WORLD_KEY],
|
||||||
|
nwOutline.OBJECT : KEY_NAME[nwKeyWords.OBJECT_KEY],
|
||||||
|
nwOutline.ENTITY : KEY_NAME[nwKeyWords.ENTITY_KEY],
|
||||||
|
nwOutline.CUSTOM : KEY_NAME[nwKeyWords.CUSTOM_KEY],
|
||||||
|
nwOutline.SYNOP : "Synopsis",
|
||||||
|
}
|
||||||
|
|
||||||
# END Class nwLabels
|
# END Class nwLabels
|
||||||
|
|
||||||
|
|||||||
@@ -88,3 +88,24 @@ class nwAlert(Enum):
|
|||||||
BUG = 3
|
BUG = 3
|
||||||
|
|
||||||
# END Enum nwAlert
|
# END Enum nwAlert
|
||||||
|
|
||||||
|
class nwOutline(Enum):
|
||||||
|
|
||||||
|
TITLE = 0
|
||||||
|
LEVEL = 1
|
||||||
|
LABEL = 2
|
||||||
|
LINE = 3
|
||||||
|
CCOUNT = 4
|
||||||
|
WCOUNT = 5
|
||||||
|
PCOUNT = 6
|
||||||
|
POV = 7
|
||||||
|
CHAR = 8
|
||||||
|
PLOT = 9
|
||||||
|
TIME = 10
|
||||||
|
WORLD = 11
|
||||||
|
OBJECT = 12
|
||||||
|
ENTITY = 13
|
||||||
|
CUSTOM = 14
|
||||||
|
SYNOP = 15
|
||||||
|
|
||||||
|
# END Enum nwOutline
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,6 @@ from nw.gui.dialogs.itemeditor import GuiItemEditor
|
|||||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||||
from nw.gui.dialogs.projectload import GuiProjectLoad
|
from nw.gui.dialogs.projectload import GuiProjectLoad
|
||||||
from nw.gui.dialogs.sessionlog import GuiSessionLogView
|
from nw.gui.dialogs.sessionlog import GuiSessionLogView
|
||||||
from nw.gui.dialogs.timelineview import GuiTimeLineView
|
|
||||||
|
|
||||||
# GUI Elements
|
# GUI Elements
|
||||||
from nw.gui.elements.docdetails import GuiDocDetails
|
from nw.gui.elements.docdetails import GuiDocDetails
|
||||||
@@ -23,6 +22,7 @@ from nw.gui.elements.doceditor import GuiDocEditor
|
|||||||
from nw.gui.elements.doctree import GuiDocTree
|
from nw.gui.elements.doctree import GuiDocTree
|
||||||
from nw.gui.elements.docviewer import GuiDocViewer
|
from nw.gui.elements.docviewer import GuiDocViewer
|
||||||
from nw.gui.elements.noticebar import GuiNoticeBar
|
from nw.gui.elements.noticebar import GuiNoticeBar
|
||||||
|
from nw.gui.elements.outline import GuiProjectOutline
|
||||||
from nw.gui.elements.searchbar import GuiSearchBar
|
from nw.gui.elements.searchbar import GuiSearchBar
|
||||||
from nw.gui.elements.viewdetails import GuiDocViewDetails
|
from nw.gui.elements.viewdetails import GuiDocViewDetails
|
||||||
|
|
||||||
@@ -43,12 +43,12 @@ __all__ = [
|
|||||||
"GuiProjectEditor",
|
"GuiProjectEditor",
|
||||||
"GuiProjectLoad",
|
"GuiProjectLoad",
|
||||||
"GuiSessionLogView",
|
"GuiSessionLogView",
|
||||||
"GuiTimeLineView",
|
|
||||||
"GuiDocDetails",
|
"GuiDocDetails",
|
||||||
"GuiDocEditor",
|
"GuiDocEditor",
|
||||||
"GuiDocTree",
|
"GuiDocTree",
|
||||||
"GuiDocViewer",
|
"GuiDocViewer",
|
||||||
"GuiNoticeBar",
|
"GuiNoticeBar",
|
||||||
|
"GuiProjectOutline",
|
||||||
"GuiSearchBar",
|
"GuiSearchBar",
|
||||||
"GuiDocViewDetails",
|
"GuiDocViewDetails",
|
||||||
"GuiDocHighlighter",
|
"GuiDocHighlighter",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from nw.gui.dialogs.itemeditor import GuiItemEditor
|
|||||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||||
from nw.gui.dialogs.projectload import GuiProjectLoad
|
from nw.gui.dialogs.projectload import GuiProjectLoad
|
||||||
from nw.gui.dialogs.sessionlog import GuiSessionLogView
|
from nw.gui.dialogs.sessionlog import GuiSessionLogView
|
||||||
from nw.gui.dialogs.timelineview import GuiTimeLineView
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GuiConfigEditor",
|
"GuiConfigEditor",
|
||||||
@@ -19,5 +18,4 @@ __all__ = [
|
|||||||
"GuiProjectEditor",
|
"GuiProjectEditor",
|
||||||
"GuiProjectLoad",
|
"GuiProjectLoad",
|
||||||
"GuiSessionLogView",
|
"GuiSessionLogView",
|
||||||
"GuiTimeLineView",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ class GuiConfigEditor(QDialog):
|
|||||||
self.setWindowTitle("Preferences")
|
self.setWindowTitle("Preferences")
|
||||||
self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64))
|
self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64))
|
||||||
|
|
||||||
self.tabMain = GuiConfigEditGeneral(self.theParent)
|
self.tabMain = GuiConfigEditGeneral(self.theParent)
|
||||||
self.tabEditor = GuiConfigEditEditor(self.theParent)
|
self.tabEditor = GuiConfigEditEditor(self.theParent)
|
||||||
|
|
||||||
self.tabWidget = QTabWidget()
|
self.tabWidget = QTabWidget()
|
||||||
self.tabWidget.addTab(self.tabMain, "General")
|
self.tabWidget.addTab(self.tabMain, "General")
|
||||||
self.tabWidget.addTab(self.tabEditor, "Editor")
|
self.tabWidget.addTab(self.tabEditor, "Editor")
|
||||||
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
@@ -79,11 +79,11 @@ class GuiConfigEditor(QDialog):
|
|||||||
validEntries = True
|
validEntries = True
|
||||||
needsRestart = False
|
needsRestart = False
|
||||||
|
|
||||||
retA, retB = self.tabMain.saveValues()
|
retA, retB = self.tabMain.saveValues()
|
||||||
validEntries &= retA
|
validEntries &= retA
|
||||||
needsRestart |= retB
|
needsRestart |= retB
|
||||||
|
|
||||||
retA, retB = self.tabEditor.saveValues()
|
retA, retB = self.tabEditor.saveValues()
|
||||||
validEntries &= retA
|
validEntries &= retA
|
||||||
needsRestart |= retB
|
needsRestart |= retB
|
||||||
|
|
||||||
|
|||||||
@@ -162,10 +162,10 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
self.colChanged = False
|
self.colChanged = False
|
||||||
self.selColour = None
|
self.selColour = None
|
||||||
|
|
||||||
self.mainBox = QHBoxLayout()
|
self.mainBox = QHBoxLayout()
|
||||||
self.mainForm = QVBoxLayout()
|
self.mainForm = QVBoxLayout()
|
||||||
|
|
||||||
self.listBox = QListWidget()
|
self.listBox = QListWidget()
|
||||||
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
|
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
|
||||||
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
||||||
self.listBox.model().rowsMoved.connect(self._rowsMoved)
|
self.listBox.model().rowsMoved.connect(self._rowsMoved)
|
||||||
|
|||||||
@@ -1,261 +0,0 @@
|
|||||||
# -*- 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 PyQt5.QtCore import Qt
|
|
||||||
from PyQt5.QtGui import QColor, QPixmap
|
|
||||||
from PyQt5.QtWidgets import (
|
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QLabel,
|
|
||||||
QDialogButtonBox, QPushButton, QHeaderView, QGridLayout, QGroupBox,
|
|
||||||
QCheckBox
|
|
||||||
)
|
|
||||||
|
|
||||||
from nw.constants import nwFiles, 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 = self.theProject.optState
|
|
||||||
|
|
||||||
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.getInt("GuiTimeLine", "winWidth", 700), 700, 10000, 700
|
|
||||||
)
|
|
||||||
winHeight = self.optState.validIntRange(
|
|
||||||
self.optState.getInt("GuiTimeLine", "winHeight", 400), 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.getBool("GuiTimeLine", "fPlot", True)
|
|
||||||
)
|
|
||||||
self.filterPlot.stateChanged.connect(self._filterChange)
|
|
||||||
|
|
||||||
self.filterChar = QCheckBox("Character tags", self)
|
|
||||||
self.filterChar.setChecked(
|
|
||||||
self.optState.getBool("GuiTimeLine", "fChar", True)
|
|
||||||
)
|
|
||||||
self.filterChar.stateChanged.connect(self._filterChange)
|
|
||||||
|
|
||||||
self.filterWorld = QCheckBox("Location tags", self)
|
|
||||||
self.filterWorld.setChecked(
|
|
||||||
self.optState.getBool("GuiTimeLine", "fWorld", True)
|
|
||||||
)
|
|
||||||
self.filterWorld.stateChanged.connect(self._filterChange)
|
|
||||||
|
|
||||||
self.filterTime = QCheckBox("Timeline tags", self)
|
|
||||||
self.filterTime.setChecked(
|
|
||||||
self.optState.getBool("GuiTimeLine", "fTime", True)
|
|
||||||
)
|
|
||||||
self.filterTime.stateChanged.connect(self._filterChange)
|
|
||||||
|
|
||||||
self.filterObject = QCheckBox("Object tags", self)
|
|
||||||
self.filterObject.setChecked(
|
|
||||||
self.optState.getBool("GuiTimeLine", "fObject", True)
|
|
||||||
)
|
|
||||||
self.filterObject.stateChanged.connect(self._filterChange)
|
|
||||||
|
|
||||||
self.filterCustom = QCheckBox("Custom tags", self)
|
|
||||||
self.filterCustom.setChecked(
|
|
||||||
self.optState.getBool("GuiTimeLine", "fCustom", True)
|
|
||||||
)
|
|
||||||
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.getBool("GuiTimeLine", "hUnused", True)
|
|
||||||
)
|
|
||||||
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.setValue("GuiTimeLine", "winWidth", winWidth)
|
|
||||||
self.optState.setValue("GuiTimeLine", "winHeight", winHeight)
|
|
||||||
self.optState.setValue("GuiTimeLine", "fPlot", fPlot)
|
|
||||||
self.optState.setValue("GuiTimeLine", "fChar", fChar)
|
|
||||||
self.optState.setValue("GuiTimeLine", "fWorld", fWorld)
|
|
||||||
self.optState.setValue("GuiTimeLine", "fTime", fTime)
|
|
||||||
self.optState.setValue("GuiTimeLine", "fObject", fObject)
|
|
||||||
self.optState.setValue("GuiTimeLine", "fCustom", fCustom)
|
|
||||||
self.optState.setValue("GuiTimeLine", "hUnused", hUnused)
|
|
||||||
|
|
||||||
self.optState.saveSettings()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def _filterChange(self, checkState):
|
|
||||||
self._buildNovelList()
|
|
||||||
return
|
|
||||||
|
|
||||||
# END Class GuiTimeLineView
|
|
||||||
@@ -5,6 +5,7 @@ from nw.gui.elements.doceditor import GuiDocEditor
|
|||||||
from nw.gui.elements.doctree import GuiDocTree
|
from nw.gui.elements.doctree import GuiDocTree
|
||||||
from nw.gui.elements.docviewer import GuiDocViewer
|
from nw.gui.elements.docviewer import GuiDocViewer
|
||||||
from nw.gui.elements.noticebar import GuiNoticeBar
|
from nw.gui.elements.noticebar import GuiNoticeBar
|
||||||
|
from nw.gui.elements.outline import GuiProjectOutline
|
||||||
from nw.gui.elements.searchbar import GuiSearchBar
|
from nw.gui.elements.searchbar import GuiSearchBar
|
||||||
from nw.gui.elements.viewdetails import GuiDocViewDetails
|
from nw.gui.elements.viewdetails import GuiDocViewDetails
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ __all__ = [
|
|||||||
"GuiDocTree",
|
"GuiDocTree",
|
||||||
"GuiDocViewer",
|
"GuiDocViewer",
|
||||||
"GuiNoticeBar",
|
"GuiNoticeBar",
|
||||||
|
"GuiProjectOutline",
|
||||||
"GuiSearchBar",
|
"GuiSearchBar",
|
||||||
"GuiDocViewDetails",
|
"GuiDocViewDetails",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,446 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter GUI Project Outline
|
||||||
|
|
||||||
|
novelWriter – GUI Project Outline
|
||||||
|
===================================
|
||||||
|
Class holding the project outline view
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2019-11-16 [0.4.1]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import nw
|
||||||
|
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView
|
||||||
|
)
|
||||||
|
|
||||||
|
from nw.constants import nwKeyWords, nwLabels, nwOutline
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class GuiProjectOutline(QTreeWidget):
|
||||||
|
|
||||||
|
DEF_WIDTH = {
|
||||||
|
nwOutline.TITLE : 200,
|
||||||
|
nwOutline.LEVEL : 40,
|
||||||
|
nwOutline.LABEL : 150,
|
||||||
|
nwOutline.LINE : 40,
|
||||||
|
nwOutline.CCOUNT : 50,
|
||||||
|
nwOutline.WCOUNT : 50,
|
||||||
|
nwOutline.PCOUNT : 50,
|
||||||
|
nwOutline.POV : 100,
|
||||||
|
nwOutline.CHAR : 100,
|
||||||
|
nwOutline.PLOT : 100,
|
||||||
|
nwOutline.TIME : 100,
|
||||||
|
nwOutline.WORLD : 100,
|
||||||
|
nwOutline.OBJECT : 100,
|
||||||
|
nwOutline.ENTITY : 100,
|
||||||
|
nwOutline.CUSTOM : 100,
|
||||||
|
nwOutline.SYNOP : 200,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_HIDDEN = {
|
||||||
|
nwOutline.TITLE : False,
|
||||||
|
nwOutline.LEVEL : True,
|
||||||
|
nwOutline.LABEL : False,
|
||||||
|
nwOutline.LINE : True,
|
||||||
|
nwOutline.CCOUNT : True,
|
||||||
|
nwOutline.WCOUNT : False,
|
||||||
|
nwOutline.PCOUNT : False,
|
||||||
|
nwOutline.POV : False,
|
||||||
|
nwOutline.CHAR : False,
|
||||||
|
nwOutline.PLOT : False,
|
||||||
|
nwOutline.TIME : True,
|
||||||
|
nwOutline.WORLD : False,
|
||||||
|
nwOutline.OBJECT : True,
|
||||||
|
nwOutline.ENTITY : True,
|
||||||
|
nwOutline.CUSTOM : True,
|
||||||
|
nwOutline.SYNOP : False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, theParent, theProject):
|
||||||
|
QTreeWidget.__init__(self, theParent)
|
||||||
|
|
||||||
|
logger.debug("Initialising ProjectOutline ...")
|
||||||
|
|
||||||
|
self.mainConf = nw.CONFIG
|
||||||
|
self.theParent = theParent
|
||||||
|
self.theProject = theProject
|
||||||
|
self.theIndex = self.theParent.theIndex
|
||||||
|
self.optState = self.theProject.optState
|
||||||
|
self.headerMenu = GuiOutlineHeaderMenu(self)
|
||||||
|
|
||||||
|
self.firstView = True
|
||||||
|
self.lastBuild = 0
|
||||||
|
|
||||||
|
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
|
self.setExpandsOnDoubleClick(False)
|
||||||
|
self.setDragEnabled(False)
|
||||||
|
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||||
|
|
||||||
|
self.treeHead = self.header()
|
||||||
|
self.treeHead.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.treeHead.customContextMenuRequested.connect(self._headerRightClick)
|
||||||
|
self.treeHead.sectionMoved.connect(self._columnMoved)
|
||||||
|
|
||||||
|
self.treeMap = {}
|
||||||
|
self.treeOrder = []
|
||||||
|
self.colWidth = {}
|
||||||
|
self.colHidden = {}
|
||||||
|
self.colIndex = {}
|
||||||
|
self.treeNCols = 0
|
||||||
|
|
||||||
|
self.initOutline()
|
||||||
|
self.headerMenu.setHiddenState(self.colHidden)
|
||||||
|
|
||||||
|
logger.debug("ProjectOutline initialisation complete")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def initOutline(self):
|
||||||
|
"""Set the default values for the Outline tree.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.treeOrder = []
|
||||||
|
self.colWidth = {}
|
||||||
|
self.colHidden = {}
|
||||||
|
self.colIndex = {}
|
||||||
|
self.treeNCols = 0
|
||||||
|
|
||||||
|
for hItem in nwOutline:
|
||||||
|
self.treeOrder.append(hItem)
|
||||||
|
self.colWidth[hItem] = self.DEF_WIDTH[hItem]
|
||||||
|
self.colHidden[hItem] = self.DEF_HIDDEN[hItem]
|
||||||
|
|
||||||
|
self.treeNCols = len(self.treeOrder)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def refreshTree(self, overRide=False):
|
||||||
|
"""Called whenever the Outline tab is activated and controls
|
||||||
|
what data to load, and if necessary, force a rebuild of the
|
||||||
|
tree.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If it's the first time, we always build
|
||||||
|
if self.firstView or overRide:
|
||||||
|
self._loadHeaderState()
|
||||||
|
self._populateTree()
|
||||||
|
self.firstView = False
|
||||||
|
return
|
||||||
|
|
||||||
|
# If the novel index has changed since the tree was last built,
|
||||||
|
# we rebuild the tree from the updated index.
|
||||||
|
lastChange = self.theParent.theIndex.timeNovel
|
||||||
|
logger.verbose("Last outline build: %.3f" % self.lastBuild)
|
||||||
|
logger.verbose("Novel index change: %.3f" % lastChange)
|
||||||
|
|
||||||
|
doBuild = lastChange > self.lastBuild and self.theProject.autoOutline
|
||||||
|
if doBuild or overRide:
|
||||||
|
logger.debug("Rebuilding Project Outline")
|
||||||
|
self._populateTree()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def closeOutline(self):
|
||||||
|
"""Called before a project is closed.
|
||||||
|
"""
|
||||||
|
self._saveHeaderState()
|
||||||
|
self.clear()
|
||||||
|
self.firstView = True
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
def _treeDoubleClick(self, tItem, tCol):
|
||||||
|
print(tItem, tCol)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _headerRightClick(self, clickPos):
|
||||||
|
"""Show the header column menu.
|
||||||
|
"""
|
||||||
|
self.headerMenu.exec_(self.mapToGlobal(clickPos))
|
||||||
|
return
|
||||||
|
|
||||||
|
def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx):
|
||||||
|
"""Make sure the order array is up to date with the actual order
|
||||||
|
of the columns.
|
||||||
|
"""
|
||||||
|
self.treeOrder.insert(newVisualIdx, self.treeOrder.pop(oldVisualIdx))
|
||||||
|
return
|
||||||
|
|
||||||
|
def _menuColumnToggled(self, isChecked, theItem):
|
||||||
|
"""Receive the changes to column visibility forwarded by the
|
||||||
|
header context menu.
|
||||||
|
"""
|
||||||
|
logger.verbose("User toggled Outline column '%s'" % theItem.name)
|
||||||
|
if theItem in self.colIndex:
|
||||||
|
self.setColumnHidden(self.colIndex[theItem], not isChecked)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def _loadHeaderState(self):
|
||||||
|
"""Load the state of the main tree header, that is, column order
|
||||||
|
and column width.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Load whatever we saved last time, regardless of wether it
|
||||||
|
# contains the correct names or number of columns. The names
|
||||||
|
# must be valid though.
|
||||||
|
tempOrder = self.optState.getValue("GuiProjectOutline", "headerOrder", [])
|
||||||
|
treeOrder = []
|
||||||
|
for hName in tempOrder:
|
||||||
|
try:
|
||||||
|
treeOrder.append(nwOutline[hName])
|
||||||
|
except:
|
||||||
|
logger.warning("Ignored unknown outline column '%s'" % str(hName))
|
||||||
|
|
||||||
|
# Add columns that was not in the file to the treeOrder array.
|
||||||
|
for hItem in nwOutline:
|
||||||
|
if hItem not in treeOrder:
|
||||||
|
treeOrder.append(hItem)
|
||||||
|
|
||||||
|
# Check that we now have a complete list, and only if so, save
|
||||||
|
# the order loaded from file. Otherwise, we keep the default.
|
||||||
|
if len(treeOrder) == self.treeNCols:
|
||||||
|
self.treeOrder = treeOrder
|
||||||
|
else:
|
||||||
|
logger.error("Failed to extract outline column order from previous session")
|
||||||
|
logger.error("Column count doesn't match %d != %d" % (len(treeOrder), self.treeNCols))
|
||||||
|
|
||||||
|
# We load whatever column widths and hidden states we find in
|
||||||
|
# the file, and leave the rest in their default state.
|
||||||
|
tmpWidth = self.optState.getValue("GuiProjectOutline", "columnWidth", {})
|
||||||
|
for hName in tmpWidth:
|
||||||
|
try:
|
||||||
|
self.colWidth[nwOutline[hName]] = tmpWidth[hName]
|
||||||
|
except:
|
||||||
|
logger.warning("Ignored unknown outline column '%s'" % str(hName))
|
||||||
|
|
||||||
|
tmpHidden = self.optState.getValue("GuiProjectOutline", "columnHidden", {})
|
||||||
|
for hName in tmpHidden:
|
||||||
|
try:
|
||||||
|
self.colHidden[nwOutline[hName]] = tmpHidden[hName]
|
||||||
|
except:
|
||||||
|
logger.warning("Ignored unknown outline column '%s'" % str(hName))
|
||||||
|
|
||||||
|
self.headerMenu.setHiddenState(self.colHidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _saveHeaderState(self):
|
||||||
|
"""Save the state of the main tree header, that is, column
|
||||||
|
order, column width and column hidden state. We don't want to
|
||||||
|
save the current width of hidden columns though. This preserves
|
||||||
|
the last known width in case they're unhidden again.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If we haven't built the tree, there is nothing to save.
|
||||||
|
if self.lastBuild == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
treeOrder = []
|
||||||
|
colWidth = {}
|
||||||
|
colHidden = {}
|
||||||
|
|
||||||
|
for hItem in nwOutline:
|
||||||
|
colWidth[hItem.name] = self.colWidth[hItem]
|
||||||
|
colHidden[hItem.name] = self.colHidden[hItem]
|
||||||
|
|
||||||
|
for iCol in range(self.columnCount()):
|
||||||
|
hName = self.treeOrder[iCol].name
|
||||||
|
treeOrder.append(hName)
|
||||||
|
|
||||||
|
iLog = self.treeHead.logicalIndex(iCol)
|
||||||
|
logWidth = self.columnWidth(iLog)
|
||||||
|
logHidden = self.isColumnHidden(iLog)
|
||||||
|
|
||||||
|
colHidden[hName] = logHidden
|
||||||
|
if not logHidden and logWidth > 0:
|
||||||
|
colWidth[hName] = logWidth
|
||||||
|
|
||||||
|
self.optState.setValue("GuiProjectOutline", "headerOrder", treeOrder)
|
||||||
|
self.optState.setValue("GuiProjectOutline", "columnWidth", colWidth)
|
||||||
|
self.optState.setValue("GuiProjectOutline", "columnHidden", colHidden)
|
||||||
|
self.optState.saveSettings()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _populateTree(self):
|
||||||
|
"""Build the tree based on the project index.
|
||||||
|
"""
|
||||||
|
|
||||||
|
theLabels = []
|
||||||
|
for i, hItem in enumerate(self.treeOrder):
|
||||||
|
theLabels.append(nwLabels.OUTLINE_COLS[hItem])
|
||||||
|
self.colIndex[hItem] = i
|
||||||
|
|
||||||
|
self.clear()
|
||||||
|
self.setHeaderLabels(theLabels)
|
||||||
|
for hItem in self.treeOrder:
|
||||||
|
self.setColumnWidth(self.colIndex[hItem], self.colWidth[hItem])
|
||||||
|
self.setColumnHidden(self.colIndex[hItem], self.colHidden[hItem])
|
||||||
|
|
||||||
|
headItem = self.headerItem()
|
||||||
|
headItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight)
|
||||||
|
headItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight)
|
||||||
|
headItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight)
|
||||||
|
|
||||||
|
currTitle = None
|
||||||
|
currChapter = None
|
||||||
|
currScene = None
|
||||||
|
|
||||||
|
for titleKey in self.theIndex.getNovelStructure():
|
||||||
|
|
||||||
|
if len(titleKey) < 16:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tHandle = titleKey[:13]
|
||||||
|
sTitle = titleKey[14:]
|
||||||
|
|
||||||
|
if tHandle not in self.theIndex.novelIndex:
|
||||||
|
continue
|
||||||
|
if sTitle not in self.theIndex.novelIndex[tHandle]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tLevel = self.theIndex.novelIndex[tHandle][sTitle]["level"]
|
||||||
|
tTime = self.theIndex.novelIndex[tHandle][sTitle]["updated"]
|
||||||
|
tItem = self._createTreeItem(tHandle, sTitle)
|
||||||
|
self.treeMap[titleKey] = tItem
|
||||||
|
|
||||||
|
if tLevel == "H1":
|
||||||
|
currTitle = tItem
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
elif tLevel == "H2":
|
||||||
|
if currTitle is None:
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
else:
|
||||||
|
currTitle.addChild(tItem)
|
||||||
|
currChapter = tItem
|
||||||
|
elif tLevel == "H3":
|
||||||
|
if currChapter is None:
|
||||||
|
if currTitle is None:
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
else:
|
||||||
|
currTitle.addChild(tItem)
|
||||||
|
else:
|
||||||
|
currChapter.addChild(tItem)
|
||||||
|
currScene = tItem
|
||||||
|
elif tLevel == "H4":
|
||||||
|
if currScene is None:
|
||||||
|
if currChapter is None:
|
||||||
|
if currTitle is None:
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
else:
|
||||||
|
currTitle.addChild(tItem)
|
||||||
|
else:
|
||||||
|
currChapter.addChild(tItem)
|
||||||
|
else:
|
||||||
|
currScene.addChild(tItem)
|
||||||
|
|
||||||
|
tItem.setExpanded(True)
|
||||||
|
|
||||||
|
self.lastBuild = time()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _createTreeItem(self, tHandle, sTitle):
|
||||||
|
"""Populate a tree item with all the column values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
nwItem = self.theProject.getItem(tHandle)
|
||||||
|
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
|
||||||
|
|
||||||
|
newItem = QTreeWidgetItem()
|
||||||
|
|
||||||
|
newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"])
|
||||||
|
newItem.setText(self.colIndex[nwOutline.LEVEL], novIdx["level"])
|
||||||
|
newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName)
|
||||||
|
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:])
|
||||||
|
newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"])
|
||||||
|
newItem.setText(self.colIndex[nwOutline.CCOUNT], str(novIdx["cCount"]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.WCOUNT], str(novIdx["wCount"]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.PCOUNT], str(novIdx["pCount"]))
|
||||||
|
newItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight)
|
||||||
|
newItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight)
|
||||||
|
newItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight)
|
||||||
|
|
||||||
|
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||||
|
newItem.setText(self.colIndex[nwOutline.POV], ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.PLOT], ", ".join(theRefs[nwKeyWords.PLOT_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.TIME], ", ".join(theRefs[nwKeyWords.TIME_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.WORLD], ", ".join(theRefs[nwKeyWords.WORLD_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.OBJECT], ", ".join(theRefs[nwKeyWords.OBJECT_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.ENTITY], ", ".join(theRefs[nwKeyWords.ENTITY_KEY]))
|
||||||
|
newItem.setText(self.colIndex[nwOutline.CUSTOM], ", ".join(theRefs[nwKeyWords.CUSTOM_KEY]))
|
||||||
|
|
||||||
|
return newItem
|
||||||
|
|
||||||
|
# END Class GuiProjectOutline
|
||||||
|
|
||||||
|
class GuiOutlineHeaderMenu(QMenu):
|
||||||
|
|
||||||
|
def __init__(self, theParent):
|
||||||
|
QMenu.__init__(self, theParent)
|
||||||
|
|
||||||
|
self.theParent = theParent
|
||||||
|
self.acceptToggle = True
|
||||||
|
|
||||||
|
mnuHead = QAction("Select Columns", self)
|
||||||
|
self.addAction(mnuHead)
|
||||||
|
self.addSeparator()
|
||||||
|
|
||||||
|
self.actionMap = {}
|
||||||
|
for hItem in nwOutline:
|
||||||
|
if hItem == nwOutline.TITLE:
|
||||||
|
continue
|
||||||
|
self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self)
|
||||||
|
self.actionMap[hItem].setCheckable(True)
|
||||||
|
self.actionMap[hItem].toggled.connect(
|
||||||
|
lambda isChecked, tItem=hItem : self._columnToggled(isChecked, tItem)
|
||||||
|
)
|
||||||
|
self.addAction(self.actionMap[hItem])
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def setHiddenState(self, hiddenState):
|
||||||
|
"""Overwrite the checked state of the columns as the inverse of
|
||||||
|
the hidden state. Skip the TITLE column as it cannot be hidden.
|
||||||
|
"""
|
||||||
|
self.acceptToggle = False
|
||||||
|
|
||||||
|
for hItem in nwOutline:
|
||||||
|
if hItem == nwOutline.TITLE or hItem not in hiddenState:
|
||||||
|
continue
|
||||||
|
self.actionMap[hItem].setChecked(not hiddenState[hItem])
|
||||||
|
|
||||||
|
self.acceptToggle = True
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
def _columnToggled(self, isChecked, theItem):
|
||||||
|
"""The user has toggled the visibility of a column. Forward the
|
||||||
|
event to the parent class only if we're accepting changes.
|
||||||
|
"""
|
||||||
|
if self.acceptToggle:
|
||||||
|
self.theParent._menuColumnToggled(isChecked, theItem)
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class GuiOutlineHeaderMenu
|
||||||
@@ -85,7 +85,7 @@ class GuiDocViewDetails(QWidget):
|
|||||||
if self.isSticky.isChecked():
|
if self.isSticky.isChecked():
|
||||||
return
|
return
|
||||||
|
|
||||||
theRefs = self.theParent.theIndex.buildReferenceList(tHandle)
|
theRefs = self.theParent.theIndex.getBackReferenceList(tHandle)
|
||||||
theList = []
|
theList = []
|
||||||
for tHandle in theRefs:
|
for tHandle in theRefs:
|
||||||
tItem = self.theProject.getItem(tHandle)
|
tItem = self.theProject.getItem(tHandle)
|
||||||
|
|||||||
+31
-10
@@ -69,6 +69,13 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aSpellCheck.setChecked(theMode)
|
self.aSpellCheck.setChecked(theMode)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setAutoOutline(self, theMode):
|
||||||
|
"""Set the auto outline check box to theMode. Used during
|
||||||
|
initialisation.
|
||||||
|
"""
|
||||||
|
self.aAutoOutline.setChecked(theMode)
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Menu Action
|
# Menu Action
|
||||||
##
|
##
|
||||||
@@ -85,6 +92,12 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.theParent.docEditor.setSpellCheck(None)
|
self.theParent.docEditor.setSpellCheck(None)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _toggleAutoOutline(self, theMode):
|
||||||
|
"""Toggle auto outline when the menu entry is checked.
|
||||||
|
"""
|
||||||
|
self.theProject.setAutoOutline(theMode)
|
||||||
|
return True
|
||||||
|
|
||||||
def _toggleViewComments(self):
|
def _toggleViewComments(self):
|
||||||
self.mainConf.setViewComments(self.aViewDocComments.isChecked())
|
self.mainConf.setViewComments(self.aViewDocComments.isChecked())
|
||||||
self.theParent.docViewer.reloadText()
|
self.theParent.docViewer.reloadText()
|
||||||
@@ -396,16 +409,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode)
|
self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode)
|
||||||
self.viewMenu.addAction(self.aFullScreen)
|
self.viewMenu.addAction(self.aFullScreen)
|
||||||
|
|
||||||
# View > Separator
|
|
||||||
self.viewMenu.addSeparator()
|
|
||||||
|
|
||||||
# View > Project Timeline
|
|
||||||
self.aViewTimeLine = QAction("Show Project Timeline", self)
|
|
||||||
self.aViewTimeLine.setStatusTip("Open the project timeline window")
|
|
||||||
self.aViewTimeLine.setShortcut("Ctrl+T")
|
|
||||||
self.aViewTimeLine.triggered.connect(self.theParent.showTimeLineDialog)
|
|
||||||
self.viewMenu.addAction(self.aViewTimeLine)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _buildEditMenu(self):
|
def _buildEditMenu(self):
|
||||||
@@ -656,6 +659,24 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aRebuildIndex.triggered.connect(self.theParent.rebuildIndex)
|
self.aRebuildIndex.triggered.connect(self.theParent.rebuildIndex)
|
||||||
self.toolsMenu.addAction(self.aRebuildIndex)
|
self.toolsMenu.addAction(self.aRebuildIndex)
|
||||||
|
|
||||||
|
# Tools > Rebuild Outline
|
||||||
|
self.aRebuildOutline = QAction("Rebuild Outline", self)
|
||||||
|
self.aRebuildOutline.setStatusTip("Rebuild the novel outline tree")
|
||||||
|
self.aRebuildOutline.setShortcut("F10")
|
||||||
|
self.aRebuildOutline.triggered.connect(self.theParent.rebuildOutline)
|
||||||
|
self.toolsMenu.addAction(self.aRebuildOutline)
|
||||||
|
|
||||||
|
# Tools > Toggle Auto Build Outline
|
||||||
|
self.aAutoOutline = QAction("Auto-Update Outline", self)
|
||||||
|
self.aAutoOutline.setStatusTip("Update project outline when a novel file is changed")
|
||||||
|
self.aAutoOutline.setCheckable(True)
|
||||||
|
self.aAutoOutline.toggled.connect(self._toggleAutoOutline)
|
||||||
|
self.aAutoOutline.setShortcut("Ctrl+F10")
|
||||||
|
self.toolsMenu.addAction(self.aAutoOutline)
|
||||||
|
|
||||||
|
# Tools > Separator
|
||||||
|
self.toolsMenu.addSeparator()
|
||||||
|
|
||||||
# Tools > Backup
|
# Tools > Backup
|
||||||
self.aBackupProject = QAction("Backup Project", self)
|
self.aBackupProject = QAction("Backup Project", self)
|
||||||
self.aBackupProject.setStatusTip("Backup Project")
|
self.aBackupProject.setStatusTip("Backup Project")
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ class GuiTheme:
|
|||||||
self.colSpell = [ 0, 0, 0]
|
self.colSpell = [ 0, 0, 0]
|
||||||
self.colTagErr = [ 0, 0, 0]
|
self.colTagErr = [ 0, 0, 0]
|
||||||
self.colRepTag = [ 0, 0, 0]
|
self.colRepTag = [ 0, 0, 0]
|
||||||
|
self.colMod = [ 0, 0, 0]
|
||||||
|
|
||||||
# Changeable Settings
|
# Changeable Settings
|
||||||
self.guiTheme = None
|
self.guiTheme = None
|
||||||
@@ -217,6 +218,7 @@ class GuiTheme:
|
|||||||
self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline")
|
self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline")
|
||||||
self.colTagErr = self._loadColour(confParser, cnfSec, "tagerror")
|
self.colTagErr = self._loadColour(confParser, cnfSec, "tagerror")
|
||||||
self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
|
self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
|
||||||
|
self.colMod = self._loadColour(confParser, cnfSec, "modifier")
|
||||||
|
|
||||||
logger.info("Loaded syntax theme '%s'" % self.guiSyntax)
|
logger.info("Loaded syntax theme '%s'" % self.guiSyntax)
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.colSpell = QColor(*self.theTheme.colSpell)
|
self.colSpell = QColor(*self.theTheme.colSpell)
|
||||||
self.colTagErr = QColor(*self.theTheme.colTagErr)
|
self.colTagErr = QColor(*self.theTheme.colTagErr)
|
||||||
self.colRepTag = QColor(*self.theTheme.colRepTag)
|
self.colRepTag = QColor(*self.theTheme.colRepTag)
|
||||||
|
self.colMod = QColor(*self.theTheme.colMod)
|
||||||
self.colTrail = QColor(*self.theTheme.colEmph,64)
|
self.colTrail = QColor(*self.theTheme.colEmph,64)
|
||||||
|
|
||||||
self.hStyles = {
|
self.hStyles = {
|
||||||
@@ -98,6 +99,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
"replace" : self._makeFormat(self.colRepTag),
|
"replace" : self._makeFormat(self.colRepTag),
|
||||||
"hidden" : self._makeFormat(self.colComm),
|
"hidden" : self._makeFormat(self.colComm),
|
||||||
"keyword" : self._makeFormat(self.colKey),
|
"keyword" : self._makeFormat(self.colKey),
|
||||||
|
"modifier" : self._makeFormat(self.colMod),
|
||||||
"value" : self._makeFormat(self.colVal),
|
"value" : self._makeFormat(self.colVal),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +252,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.setFormat(4, len(theText), self.hStyles["header4"])
|
self.setFormat(4, len(theText), self.hStyles["header4"])
|
||||||
|
|
||||||
elif theText.startswith("%"): # Comments
|
elif theText.startswith("%"): # Comments
|
||||||
self.setFormat(0, len(theText), self.hStyles["hidden"])
|
if theText.startswith("%synopsis:"):
|
||||||
|
self.setFormat(0, 10, self.hStyles["modifier"])
|
||||||
|
self.setFormat(10, len(theText), self.hStyles["hidden"])
|
||||||
|
else:
|
||||||
|
self.setFormat(0, len(theText), self.hStyles["hidden"])
|
||||||
|
|
||||||
else: # Text Paragraph
|
else: # Text Paragraph
|
||||||
for rX, xFmt in self.rxRules:
|
for rX, xFmt in self.rxRules:
|
||||||
|
|||||||
+56
-18
@@ -21,13 +21,13 @@ from PyQt5.QtCore import Qt, QTimer
|
|||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence
|
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QShortcut,
|
qApp, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QShortcut,
|
||||||
QMessageBox, QProgressDialog, QDialog
|
QMessageBox, QProgressDialog, QDialog, QTabWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, GuiExport,
|
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, GuiExport,
|
||||||
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
||||||
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiTimeLineView,
|
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiProjectOutline,
|
||||||
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad
|
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad
|
||||||
)
|
)
|
||||||
from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
|
from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
|
||||||
@@ -76,6 +76,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.searchBar = GuiSearchBar(self)
|
self.searchBar = GuiSearchBar(self)
|
||||||
self.treeMeta = GuiDocDetails(self, self.theProject)
|
self.treeMeta = GuiDocDetails(self, self.theProject)
|
||||||
self.treeView = GuiDocTree(self, self.theProject)
|
self.treeView = GuiDocTree(self, self.theProject)
|
||||||
|
self.projView = GuiProjectOutline(self, self.theProject)
|
||||||
self.mainMenu = GuiMainMenu(self, self.theProject)
|
self.mainMenu = GuiMainMenu(self, self.theProject)
|
||||||
|
|
||||||
# Minor Gui Elements
|
# Minor Gui Elements
|
||||||
@@ -111,20 +112,33 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitView.addWidget(self.editPane)
|
self.splitView.addWidget(self.editPane)
|
||||||
self.splitView.addWidget(self.viewPane)
|
self.splitView.addWidget(self.viewPane)
|
||||||
|
|
||||||
|
self.splitOutline = QSplitter(Qt.Vertical)
|
||||||
|
self.splitOutline.addWidget(self.projView)
|
||||||
|
|
||||||
|
self.tabWidget = QTabWidget()
|
||||||
|
self.tabWidget.setTabPosition(QTabWidget.East)
|
||||||
|
self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}")
|
||||||
|
self.tabWidget.addTab(self.splitView, "Editor")
|
||||||
|
self.tabWidget.addTab(self.splitOutline, "Outline")
|
||||||
|
self.tabWidget.currentChanged.connect(self._mainTabChanged)
|
||||||
|
|
||||||
self.splitMain = QSplitter(Qt.Horizontal)
|
self.splitMain = QSplitter(Qt.Horizontal)
|
||||||
self.splitMain.setContentsMargins(4,4,4,4)
|
self.splitMain.setContentsMargins(4,4,4,4)
|
||||||
self.splitMain.setOpaqueResize(False)
|
self.splitMain.setOpaqueResize(False)
|
||||||
self.splitMain.addWidget(self.treePane)
|
self.splitMain.addWidget(self.treePane)
|
||||||
self.splitMain.addWidget(self.splitView)
|
self.splitMain.addWidget(self.tabWidget)
|
||||||
self.splitMain.setSizes(self.mainConf.mainPanePos)
|
self.splitMain.setSizes(self.mainConf.mainPanePos)
|
||||||
|
|
||||||
self.setCentralWidget(self.splitMain)
|
self.setCentralWidget(self.splitMain)
|
||||||
|
|
||||||
self.idxTree = self.splitMain.indexOf(self.treePane)
|
self.idxTree = self.splitMain.indexOf(self.treePane)
|
||||||
self.idxMain = self.splitMain.indexOf(self.splitView)
|
self.idxMain = self.splitMain.indexOf(self.tabWidget)
|
||||||
self.idxEditor = self.splitView.indexOf(self.editPane)
|
self.idxEditor = self.splitView.indexOf(self.editPane)
|
||||||
self.idxViewer = self.splitView.indexOf(self.viewPane)
|
self.idxViewer = self.splitView.indexOf(self.viewPane)
|
||||||
|
|
||||||
|
self.idxTabEdit = self.tabWidget.indexOf(self.splitView)
|
||||||
|
self.idxTabProj = self.tabWidget.indexOf(self.splitOutline)
|
||||||
|
|
||||||
self.splitMain.setCollapsible(self.idxTree, False)
|
self.splitMain.setCollapsible(self.idxTree, False)
|
||||||
self.splitMain.setCollapsible(self.idxMain, False)
|
self.splitMain.setCollapsible(self.idxMain, False)
|
||||||
self.splitView.setCollapsible(self.idxEditor, False)
|
self.splitView.setCollapsible(self.idxEditor, False)
|
||||||
@@ -296,6 +310,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
if saveOK:
|
if saveOK:
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
|
self.projView.closeOutline()
|
||||||
self.theProject.closeProject()
|
self.theProject.closeProject()
|
||||||
self.theIndex.clearIndex()
|
self.theIndex.clearIndex()
|
||||||
self.clearGUI()
|
self.clearGUI()
|
||||||
@@ -318,6 +333,9 @@ class GuiMain(QMainWindow):
|
|||||||
if not self.closeProject():
|
if not self.closeProject():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Switch main tab to editor view
|
||||||
|
self.tabWidget.setCurrentWidget(self.splitView)
|
||||||
|
|
||||||
# Try to open the project
|
# Try to open the project
|
||||||
if not self.theProject.openProject(projFile):
|
if not self.theProject.openProject(projFile):
|
||||||
if self.theProject.lockedBy is not None:
|
if self.theProject.lockedBy is not None:
|
||||||
@@ -368,6 +386,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.rebuildTree()
|
self.rebuildTree()
|
||||||
self.docEditor.setDictionaries()
|
self.docEditor.setDictionaries()
|
||||||
self.docEditor.setSpellCheck(self.theProject.spellCheck)
|
self.docEditor.setSpellCheck(self.theProject.spellCheck)
|
||||||
|
self.mainMenu.setAutoOutline(self.theProject.autoOutline)
|
||||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||||
|
|
||||||
# Restore previously open documents, if any
|
# Restore previously open documents, if any
|
||||||
@@ -420,6 +439,7 @@ class GuiMain(QMainWindow):
|
|||||||
def openDocument(self, tHandle):
|
def openDocument(self, tHandle):
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
|
self.tabWidget.setCurrentWidget(self.splitView)
|
||||||
if self.docEditor.loadText(tHandle):
|
if self.docEditor.loadText(tHandle):
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
self.theProject.setLastEdited(tHandle)
|
self.theProject.setLastEdited(tHandle)
|
||||||
@@ -446,6 +466,9 @@ class GuiMain(QMainWindow):
|
|||||||
logger.debug("No document selected, giving up")
|
logger.debug("No document selected, giving up")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Make sure main tab is in Editor view
|
||||||
|
self.tabWidget.setCurrentWidget(self.splitView)
|
||||||
|
|
||||||
if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible():
|
if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible():
|
||||||
bPos = self.splitMain.sizes()
|
bPos = self.splitMain.sizes()
|
||||||
self.viewPane.setVisible(True)
|
self.viewPane.setVisible(True)
|
||||||
@@ -615,17 +638,17 @@ class GuiMain(QMainWindow):
|
|||||||
theDoc = NWDoc(self.theProject, self)
|
theDoc = NWDoc(self.theProject, self)
|
||||||
theText = theDoc.openDocument(tHandle, False)
|
theText = theDoc.openDocument(tHandle, False)
|
||||||
|
|
||||||
# Run Word Count
|
# Build tag index
|
||||||
cC, wC, pC = countWords(theText)
|
self.theIndex.scanText(tHandle, theText)
|
||||||
|
|
||||||
|
# Get Word Counts
|
||||||
|
cC, wC, pC = self.theIndex.getCounts(tHandle)
|
||||||
tItem.setCharCount(cC)
|
tItem.setCharCount(cC)
|
||||||
tItem.setWordCount(wC)
|
tItem.setWordCount(wC)
|
||||||
tItem.setParaCount(pC)
|
tItem.setParaCount(pC)
|
||||||
self.treeView.propagateCount(tHandle, wC)
|
self.treeView.propagateCount(tHandle, wC)
|
||||||
self.treeView.projectWordCount()
|
self.treeView.projectWordCount()
|
||||||
|
|
||||||
# Build tag index
|
|
||||||
self.theIndex.scanText(tHandle, theText)
|
|
||||||
|
|
||||||
nDone += 1
|
nDone += 1
|
||||||
if dlgProg.wasCanceled():
|
if dlgProg.wasCanceled():
|
||||||
break
|
break
|
||||||
@@ -634,6 +657,14 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def rebuildOutline(self):
|
||||||
|
"""Force a rebuild of the Outline view.
|
||||||
|
"""
|
||||||
|
logger.verbose("Forcing a rebuild of the Project Outline")
|
||||||
|
self.tabWidget.setCurrentWidget(self.splitOutline)
|
||||||
|
self.projView.refreshTree(overRide=True)
|
||||||
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Main Dialogs
|
# Main Dialogs
|
||||||
##
|
##
|
||||||
@@ -696,12 +727,6 @@ class GuiMain(QMainWindow):
|
|||||||
dlgExport.exec_()
|
dlgExport.exec_()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def showTimeLineDialog(self):
|
|
||||||
if self.hasProject:
|
|
||||||
dlgTLine = GuiTimeLineView(self, self.theProject, self.theIndex)
|
|
||||||
dlgTLine.exec_()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def showSessionLogDialog(self):
|
def showSessionLogDialog(self):
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
dlgTLine = GuiSessionLogView(self, self.theProject)
|
dlgTLine = GuiSessionLogView(self, self.theProject)
|
||||||
@@ -767,7 +792,8 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info("Exiting %s" % nw.__package__)
|
logger.info("Exiting %s" % nw.__package__)
|
||||||
self.closeProject(True)
|
if self.hasProject:
|
||||||
|
self.closeProject(True)
|
||||||
|
|
||||||
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
||||||
if not self.mainConf.isFullScreen:
|
if not self.mainConf.isFullScreen:
|
||||||
@@ -817,6 +843,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.isZenMode = not self.isZenMode
|
self.isZenMode = not self.isZenMode
|
||||||
if self.isZenMode:
|
if self.isZenMode:
|
||||||
logger.debug("Activating Zen mode")
|
logger.debug("Activating Zen mode")
|
||||||
|
self.tabWidget.setCurrentWidget(self.splitView)
|
||||||
else:
|
else:
|
||||||
logger.debug("Deactivating Zen mode")
|
logger.debug("Deactivating Zen mode")
|
||||||
|
|
||||||
@@ -824,6 +851,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.treePane.setVisible(isVisible)
|
self.treePane.setVisible(isVisible)
|
||||||
self.statusBar.setVisible(isVisible)
|
self.statusBar.setVisible(isVisible)
|
||||||
self.mainMenu.setVisible(isVisible)
|
self.mainMenu.setVisible(isVisible)
|
||||||
|
self.tabWidget.tabBar().setVisible(isVisible)
|
||||||
|
|
||||||
if self.viewPane.isVisible():
|
if self.viewPane.isVisible():
|
||||||
self.viewPane.setVisible(False)
|
self.viewPane.setVisible(False)
|
||||||
@@ -866,7 +894,6 @@ class GuiMain(QMainWindow):
|
|||||||
self.addAction(self.mainMenu.aFileDetails)
|
self.addAction(self.mainMenu.aFileDetails)
|
||||||
self.addAction(self.mainMenu.aZenMode)
|
self.addAction(self.mainMenu.aZenMode)
|
||||||
self.addAction(self.mainMenu.aFullScreen)
|
self.addAction(self.mainMenu.aFullScreen)
|
||||||
self.addAction(self.mainMenu.aViewTimeLine)
|
|
||||||
self.addAction(self.mainMenu.aEditUndo)
|
self.addAction(self.mainMenu.aEditUndo)
|
||||||
self.addAction(self.mainMenu.aEditRedo)
|
self.addAction(self.mainMenu.aEditRedo)
|
||||||
self.addAction(self.mainMenu.aEditCut)
|
self.addAction(self.mainMenu.aEditCut)
|
||||||
@@ -972,7 +999,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
def _keyPressEscape(self):
|
def _keyPressEscape(self):
|
||||||
"""When the escape key is pressed somewhere in the main window,
|
"""When the escape key is pressed somewhere in the main window,
|
||||||
do the following, in order.
|
do the following, in order:
|
||||||
"""
|
"""
|
||||||
if self.searchBar.isVisible():
|
if self.searchBar.isVisible():
|
||||||
self.searchBar.setVisible(False)
|
self.searchBar.setVisible(False)
|
||||||
@@ -981,4 +1008,15 @@ class GuiMain(QMainWindow):
|
|||||||
self.toggleZenMode()
|
self.toggleZenMode()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _mainTabChanged(self, tabIndex):
|
||||||
|
"""Activated when the main window tab is changed.
|
||||||
|
"""
|
||||||
|
if tabIndex == self.idxTabEdit:
|
||||||
|
logger.verbose("Editor tab activated")
|
||||||
|
elif tabIndex == self.idxTabProj:
|
||||||
|
logger.verbose("Project outline tab activated")
|
||||||
|
if self.hasProject:
|
||||||
|
self.projView.refreshTree()
|
||||||
|
return
|
||||||
|
|
||||||
# END Class GuiMain
|
# END Class GuiMain
|
||||||
|
|||||||
+252
-114
@@ -15,10 +15,12 @@ import json
|
|||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
|
from time import time
|
||||||
|
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwFiles, nwKeyWords, nwItemType, nwItemClass, nwAlert
|
nwFiles, nwKeyWords, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||||
)
|
)
|
||||||
|
from nw.tools import countWords
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -36,14 +38,14 @@ class NWIndex():
|
|||||||
nwKeyWords.CUSTOM_KEY
|
nwKeyWords.CUSTOM_KEY
|
||||||
]
|
]
|
||||||
TAG_CLASS = {
|
TAG_CLASS = {
|
||||||
nwKeyWords.CHAR_KEY : [nwItemClass.CHARACTER, 1],
|
nwKeyWords.CHAR_KEY : nwItemClass.CHARACTER,
|
||||||
nwKeyWords.POV_KEY : [nwItemClass.CHARACTER, 2],
|
nwKeyWords.POV_KEY : nwItemClass.CHARACTER,
|
||||||
nwKeyWords.PLOT_KEY : [nwItemClass.PLOT, 1],
|
nwKeyWords.PLOT_KEY : nwItemClass.PLOT,
|
||||||
nwKeyWords.TIME_KEY : [nwItemClass.TIMELINE, 1],
|
nwKeyWords.TIME_KEY : nwItemClass.TIMELINE,
|
||||||
nwKeyWords.WORLD_KEY : [nwItemClass.WORLD, 1],
|
nwKeyWords.WORLD_KEY : nwItemClass.WORLD,
|
||||||
nwKeyWords.OBJECT_KEY : [nwItemClass.OBJECT, 1],
|
nwKeyWords.OBJECT_KEY : nwItemClass.OBJECT,
|
||||||
nwKeyWords.ENTITY_KEY : [nwItemClass.ENTITY, 1],
|
nwKeyWords.ENTITY_KEY : nwItemClass.ENTITY,
|
||||||
nwKeyWords.CUSTOM_KEY : [nwItemClass.CUSTOM, 1],
|
nwKeyWords.CUSTOM_KEY : nwItemClass.CUSTOM,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, theProject, theParent):
|
def __init__(self, theProject, theParent):
|
||||||
@@ -55,13 +57,18 @@ class NWIndex():
|
|||||||
self.indexBroken = False
|
self.indexBroken = False
|
||||||
|
|
||||||
# Indices
|
# Indices
|
||||||
self.tagIndex = {}
|
self.tagIndex = None
|
||||||
self.refIndex = {}
|
self.refIndex = None
|
||||||
self.novelIndex = {}
|
self.novelIndex = None
|
||||||
self.noteIndex = {}
|
self.noteIndex = None
|
||||||
|
self.textCounts = None
|
||||||
|
|
||||||
# Lists
|
# TimeStamps
|
||||||
self.novelList = []
|
self.timeNovel = 0
|
||||||
|
self.timeNote = 0
|
||||||
|
self.timeIndex = 0
|
||||||
|
|
||||||
|
self.clearIndex()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -70,13 +77,21 @@ class NWIndex():
|
|||||||
##
|
##
|
||||||
|
|
||||||
def clearIndex(self):
|
def clearIndex(self):
|
||||||
|
"""Clear the index dictionaries and time stamps.
|
||||||
|
"""
|
||||||
self.tagIndex = {}
|
self.tagIndex = {}
|
||||||
self.refIndex = {}
|
self.refIndex = {}
|
||||||
self.novelIndex = {}
|
self.novelIndex = {}
|
||||||
self.noteIndex = {}
|
self.noteIndex = {}
|
||||||
|
self.textCounts = {}
|
||||||
|
self.timeNovel = 0
|
||||||
|
self.timeNote = 0
|
||||||
|
self.timeIndex = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def deleteHandle(self, tHandle):
|
def deleteHandle(self, tHandle):
|
||||||
|
"""Delete all entries of a given document handle.
|
||||||
|
"""
|
||||||
|
|
||||||
delTags = []
|
delTags = []
|
||||||
for tTag in self.tagIndex:
|
for tTag in self.tagIndex:
|
||||||
@@ -89,6 +104,7 @@ class NWIndex():
|
|||||||
self.refIndex.pop(tHandle, None)
|
self.refIndex.pop(tHandle, None)
|
||||||
self.novelIndex.pop(tHandle, None)
|
self.novelIndex.pop(tHandle, None)
|
||||||
self.noteIndex.pop(tHandle, None)
|
self.noteIndex.pop(tHandle, None)
|
||||||
|
self.textCounts.pop(tHandle, None)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -100,8 +116,9 @@ class NWIndex():
|
|||||||
"""Load index from last session from the project meta folder.
|
"""Load index from last session from the project meta folder.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
theData = {}
|
theData = {}
|
||||||
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
||||||
|
|
||||||
if path.isfile(indexFile):
|
if path.isfile(indexFile):
|
||||||
logger.debug("Loading index file")
|
logger.debug("Loading index file")
|
||||||
try:
|
try:
|
||||||
@@ -121,24 +138,31 @@ class NWIndex():
|
|||||||
self.novelIndex = theData["novelIndex"]
|
self.novelIndex = theData["novelIndex"]
|
||||||
if "noteIndex" in theData.keys():
|
if "noteIndex" in theData.keys():
|
||||||
self.noteIndex = theData["noteIndex"]
|
self.noteIndex = theData["noteIndex"]
|
||||||
|
if "textCounts" in theData.keys():
|
||||||
|
self.textCounts = theData["textCounts"]
|
||||||
|
|
||||||
self.checkIndex()
|
nowTime = time()
|
||||||
|
self.timeNovel = nowTime
|
||||||
|
self.timeNote = nowTime
|
||||||
|
self.timeIndex = nowTime
|
||||||
|
|
||||||
return True
|
self.checkIndex()
|
||||||
|
|
||||||
return False
|
return True
|
||||||
|
|
||||||
def saveIndex(self):
|
def saveIndex(self):
|
||||||
"""Save the current index as a json file in the project meta
|
"""Save the current index as a json file in the project meta
|
||||||
folder.
|
data folder.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
||||||
|
|
||||||
logger.debug("Saving index file")
|
logger.debug("Saving index file")
|
||||||
if self.mainConf.debugInfo:
|
if self.mainConf.debugInfo:
|
||||||
nIndent = 2
|
nIndent = 2
|
||||||
else:
|
else:
|
||||||
nIndent = None
|
nIndent = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(indexFile,mode="w+",encoding="utf8") as outFile:
|
with open(indexFile,mode="w+",encoding="utf8") as outFile:
|
||||||
outFile.write(json.dumps({
|
outFile.write(json.dumps({
|
||||||
@@ -146,6 +170,7 @@ class NWIndex():
|
|||||||
"refIndex" : self.refIndex,
|
"refIndex" : self.refIndex,
|
||||||
"novelIndex" : self.novelIndex,
|
"novelIndex" : self.novelIndex,
|
||||||
"noteIndex" : self.noteIndex,
|
"noteIndex" : self.noteIndex,
|
||||||
|
"textCounts" : self.textCounts,
|
||||||
}, indent=nIndent))
|
}, indent=nIndent))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to save index file")
|
logger.error("Failed to save index file")
|
||||||
@@ -161,29 +186,38 @@ class NWIndex():
|
|||||||
|
|
||||||
self.indexBroken = False
|
self.indexBroken = False
|
||||||
|
|
||||||
for tTag in self.tagIndex:
|
try:
|
||||||
if len(self.tagIndex[tTag]) != 3:
|
for tTag in self.tagIndex:
|
||||||
self.indexBroken = True
|
if len(self.tagIndex[tTag]) != 3:
|
||||||
|
|
||||||
for tHandle in self.refIndex:
|
|
||||||
for tEntry in self.refIndex[tHandle]:
|
|
||||||
if len(tEntry) != 4:
|
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.novelIndex:
|
for tHandle in self.refIndex:
|
||||||
for tEntry in self.novelIndex[tHandle]:
|
for sTitle in self.refIndex[tHandle]:
|
||||||
if len(tEntry) != 4:
|
for tEntry in self.refIndex[tHandle][sTitle]["tags"]:
|
||||||
|
if len(tEntry) != 3:
|
||||||
|
self.indexBroken = True
|
||||||
|
|
||||||
|
for tHandle in self.novelIndex:
|
||||||
|
for sLine in self.novelIndex[tHandle]:
|
||||||
|
if len(self.novelIndex[tHandle][sLine].keys()) != 8:
|
||||||
|
self.indexBroken = True
|
||||||
|
|
||||||
|
for tHandle in self.noteIndex:
|
||||||
|
for sLine in self.noteIndex[tHandle]:
|
||||||
|
if len(self.noteIndex[tHandle][sLine].keys()) != 8:
|
||||||
|
self.indexBroken = True
|
||||||
|
|
||||||
|
for tHandle in self.textCounts:
|
||||||
|
if len(self.textCounts[tHandle]) != 3:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.noteIndex:
|
except:
|
||||||
for tEntry in self.noteIndex[tHandle]:
|
self.indexBroken = True
|
||||||
if len(tEntry) != 4:
|
|
||||||
self.indexBroken = True
|
|
||||||
|
|
||||||
if self.indexBroken:
|
if self.indexBroken:
|
||||||
self.clearIndex()
|
self.clearIndex()
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
"The project index loaded from cache contains errors. Triggering Rebuild Index.",
|
"The index loaded from project cache contains errors. Rebuilding index.",
|
||||||
nwAlert.WARN
|
nwAlert.WARN
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -201,23 +235,33 @@ class NWIndex():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
theItem = self.theProject.getItem(tHandle)
|
theItem = self.theProject.getItem(tHandle)
|
||||||
if theItem is None: return False
|
if theItem is None:
|
||||||
if theItem.itemType != nwItemType.FILE: return False
|
return False
|
||||||
if theItem.parHandle == self.theProject.trashRoot: return False
|
if theItem.itemType != nwItemType.FILE:
|
||||||
|
return False
|
||||||
|
if theItem.parHandle == self.theProject.trashRoot:
|
||||||
|
return False
|
||||||
|
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
||||||
|
return False
|
||||||
|
|
||||||
itemClass = theItem.itemClass
|
itemClass = theItem.itemClass
|
||||||
itemLayout = theItem.itemLayout
|
itemLayout = theItem.itemLayout
|
||||||
|
|
||||||
logger.debug("Indexing item with handle %s" % tHandle)
|
logger.debug("Indexing item with handle %s" % tHandle)
|
||||||
|
|
||||||
# Check file type, and reset its old index
|
# Check file type, and reset its old index
|
||||||
if itemClass == nwItemClass.NOVEL:
|
# Also add an entry for T0 in case the file has no title
|
||||||
self.novelIndex[tHandle] = []
|
self.refIndex[tHandle] = {}
|
||||||
self.refIndex[tHandle] = []
|
self.refIndex[tHandle]["T0"] = {
|
||||||
isNovel = True
|
"tags" : [],
|
||||||
else:
|
"updated" : time(),
|
||||||
self.noteIndex[tHandle] = []
|
}
|
||||||
self.refIndex[tHandle] = []
|
if itemLayout == nwItemLayout.NOTE:
|
||||||
|
self.noteIndex[tHandle] = {}
|
||||||
isNovel = False
|
isNovel = False
|
||||||
|
else:
|
||||||
|
self.novelIndex[tHandle] = {}
|
||||||
|
isNovel = True
|
||||||
|
|
||||||
# Also clear references to file in tag index
|
# Also clear references to file in tag index
|
||||||
clearTags = []
|
clearTags = []
|
||||||
@@ -229,19 +273,47 @@ class NWIndex():
|
|||||||
|
|
||||||
nLine = 0
|
nLine = 0
|
||||||
nTitle = 0
|
nTitle = 0
|
||||||
for aLine in theText.splitlines():
|
theLines = theText.splitlines()
|
||||||
aLine = aLine.strip()
|
for aLine in theLines:
|
||||||
|
aLine = aLine
|
||||||
nLine += 1
|
nLine += 1
|
||||||
nChar = len(aLine)
|
nChar = len(aLine.strip())
|
||||||
if nChar == 0: continue
|
if nChar == 0:
|
||||||
if aLine[0] == "#":
|
continue
|
||||||
|
|
||||||
|
if aLine.startswith(r"#"):
|
||||||
isTitle = self.indexTitle(tHandle, isNovel, aLine, nLine, itemLayout)
|
isTitle = self.indexTitle(tHandle, isNovel, aLine, nLine, itemLayout)
|
||||||
if isTitle:
|
if isTitle and nLine > 0:
|
||||||
|
if nTitle > 0:
|
||||||
|
lastText = "\n".join(theLines[nTitle-1:nLine-1])
|
||||||
|
self.indexWordCounts(tHandle, isNovel, lastText, nTitle)
|
||||||
nTitle = nLine
|
nTitle = nLine
|
||||||
elif aLine[0] == "@":
|
|
||||||
|
elif aLine.startswith(r"@"):
|
||||||
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
|
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
|
||||||
self.indexTag(tHandle, aLine, nLine, itemClass)
|
self.indexTag(tHandle, aLine, nLine, itemClass)
|
||||||
|
|
||||||
|
elif aLine.startswith(r"%synopsis:"):
|
||||||
|
if nTitle > 0:
|
||||||
|
self.indexSynopsis(tHandle, isNovel, aLine[10:].strip(), nTitle)
|
||||||
|
|
||||||
|
# Count words for remaining text after last heading
|
||||||
|
if nTitle > 0:
|
||||||
|
lastText = "\n".join(theLines[nTitle-1:nLine-1])
|
||||||
|
self.indexWordCounts(tHandle, isNovel, lastText, nTitle)
|
||||||
|
|
||||||
|
# Run word counter for whole text
|
||||||
|
cC, wC, pC = countWords(theText)
|
||||||
|
self.textCounts[tHandle] = [cC, wC, pC]
|
||||||
|
|
||||||
|
# Update timestamps for index changes
|
||||||
|
nowTime = time()
|
||||||
|
self.timeIndex = nowTime
|
||||||
|
if isNovel:
|
||||||
|
self.timeNovel = nowTime
|
||||||
|
else:
|
||||||
|
self.timeNote = nowTime
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def indexTitle(self, tHandle, isNovel, aLine, nLine, itemLayout):
|
def indexTitle(self, tHandle, isNovel, aLine, nLine, itemLayout):
|
||||||
@@ -250,30 +322,79 @@ class NWIndex():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if aLine.startswith("# "):
|
if aLine.startswith("# "):
|
||||||
hDepth = 1
|
hDepth = "H1"
|
||||||
hText = aLine[2:].strip()
|
hText = aLine[2:].strip()
|
||||||
elif aLine.startswith("## "):
|
elif aLine.startswith("## "):
|
||||||
hDepth = 2
|
hDepth = "H2"
|
||||||
hText = aLine[3:].strip()
|
hText = aLine[3:].strip()
|
||||||
elif aLine.startswith("### "):
|
elif aLine.startswith("### "):
|
||||||
hDepth = 3
|
hDepth = "H3"
|
||||||
hText = aLine[4:].strip()
|
hText = aLine[4:].strip()
|
||||||
elif aLine.startswith("#### "):
|
elif aLine.startswith("#### "):
|
||||||
hDepth = 4
|
hDepth = "H4"
|
||||||
hText = aLine[5:].strip()
|
hText = aLine[5:].strip()
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
sTitle = "T%d" % nLine
|
||||||
|
self.refIndex[tHandle][sTitle] = {
|
||||||
|
"tags" : [],
|
||||||
|
"updated" : time(),
|
||||||
|
}
|
||||||
|
theData = {
|
||||||
|
"level" : hDepth,
|
||||||
|
"title" : hText,
|
||||||
|
"layout" : itemLayout.name,
|
||||||
|
"synopsis" : "",
|
||||||
|
"cCount" : 0,
|
||||||
|
"wCount" : 0,
|
||||||
|
"pCount" : 0,
|
||||||
|
"updated" : time(),
|
||||||
|
}
|
||||||
|
|
||||||
if hText != "":
|
if hText != "":
|
||||||
if isNovel:
|
if isNovel:
|
||||||
if tHandle in self.novelIndex:
|
if tHandle in self.novelIndex:
|
||||||
self.novelIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name])
|
self.novelIndex[tHandle][sTitle] = theData
|
||||||
else:
|
else:
|
||||||
if tHandle in self.noteIndex:
|
if tHandle in self.noteIndex:
|
||||||
self.noteIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name])
|
self.noteIndex[tHandle][sTitle] = theData
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def indexWordCounts(self, tHandle, isNovel, theText, nTitle):
|
||||||
|
cC, wC, pC = countWords(theText)
|
||||||
|
sTitle = "T%d" % nTitle
|
||||||
|
if isNovel:
|
||||||
|
if tHandle in self.novelIndex:
|
||||||
|
if sTitle in self.novelIndex[tHandle]:
|
||||||
|
self.novelIndex[tHandle][sTitle]["cCount"] = cC
|
||||||
|
self.novelIndex[tHandle][sTitle]["wCount"] = wC
|
||||||
|
self.novelIndex[tHandle][sTitle]["pCount"] = pC
|
||||||
|
self.novelIndex[tHandle][sTitle]["updated"] = time()
|
||||||
|
else:
|
||||||
|
if tHandle in self.noteIndex:
|
||||||
|
if sTitle in self.noteIndex[tHandle]:
|
||||||
|
self.noteIndex[tHandle][sTitle]["cCount"] = cC
|
||||||
|
self.noteIndex[tHandle][sTitle]["wCount"] = wC
|
||||||
|
self.noteIndex[tHandle][sTitle]["pCount"] = pC
|
||||||
|
self.noteIndex[tHandle][sTitle]["updated"] = time()
|
||||||
|
return
|
||||||
|
|
||||||
|
def indexSynopsis(self, tHandle, isNovel, theText, nTitle):
|
||||||
|
sTitle = "T%d" % nTitle
|
||||||
|
if isNovel:
|
||||||
|
if tHandle in self.novelIndex:
|
||||||
|
if sTitle in self.novelIndex[tHandle]:
|
||||||
|
self.novelIndex[tHandle][sTitle]["synopsis"] = theText
|
||||||
|
self.novelIndex[tHandle][sTitle]["updated"] = time()
|
||||||
|
else:
|
||||||
|
if tHandle in self.noteIndex:
|
||||||
|
if sTitle in self.noteIndex[tHandle]:
|
||||||
|
self.noteIndex[tHandle][sTitle]["synopsis"] = theText
|
||||||
|
self.noteIndex[tHandle][sTitle]["updated"] = time()
|
||||||
|
return
|
||||||
|
|
||||||
def indexNoteRef(self, tHandle, aLine, nLine, nTitle):
|
def indexNoteRef(self, tHandle, aLine, nLine, nTitle):
|
||||||
"""Validate and save the information about a reference to a tag
|
"""Validate and save the information about a reference to a tag
|
||||||
in another file.
|
in another file.
|
||||||
@@ -283,9 +404,14 @@ class NWIndex():
|
|||||||
if not isValid or len(theBits) == 0:
|
if not isValid or len(theBits) == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
sTitle = "T%d" % nTitle
|
||||||
|
if sTitle not in self.refIndex[tHandle]:
|
||||||
|
logger.error("Cannot save tags to file %s, no title %s" % (tHandle, sTitle))
|
||||||
|
return False
|
||||||
|
|
||||||
if theBits[0] != nwKeyWords.TAG_KEY:
|
if theBits[0] != nwKeyWords.TAG_KEY:
|
||||||
for aVal in theBits[1:]:
|
for aVal in theBits[1:]:
|
||||||
self.refIndex[tHandle].append([nLine, theBits[0], aVal, nTitle])
|
self.refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -378,7 +504,7 @@ class NWIndex():
|
|||||||
# If we're still here, we better check that the references exist
|
# If we're still here, we better check that the references exist
|
||||||
for n in range(1,nBits):
|
for n in range(1,nBits):
|
||||||
if theBits[n] in self.tagIndex:
|
if theBits[n] in self.tagIndex:
|
||||||
isGood[n] = self.TAG_CLASS[theBits[0]][0].name == self.tagIndex[theBits[n]][2]
|
isGood[n] = self.TAG_CLASS[theBits[0]].name == self.tagIndex[theBits[n]][2]
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -386,20 +512,73 @@ class NWIndex():
|
|||||||
# Extract Data
|
# Extract Data
|
||||||
##
|
##
|
||||||
|
|
||||||
def buildNovelList(self):
|
def getNovelStructure(self):
|
||||||
"""Build a list of the content of the novel.
|
"""Builds a list of all titles in the novel, in the correct
|
||||||
|
order as they appear in the tree view and in the respective
|
||||||
|
document files, but skipping all note files.
|
||||||
"""
|
"""
|
||||||
self.novelList = []
|
|
||||||
self.novelOrder = []
|
theStructure = []
|
||||||
for tHandle in self.theProject.treeOrder:
|
for tHandle in self.theProject.treeOrder:
|
||||||
if tHandle not in self.novelIndex:
|
if tHandle not in self.novelIndex:
|
||||||
continue
|
continue
|
||||||
for tEntry in self.novelIndex[tHandle]:
|
for sTitle in sorted(self.novelIndex[tHandle].keys()):
|
||||||
self.novelList.append(tEntry)
|
theStructure.append("%s:%s" % (tHandle, sTitle))
|
||||||
self.novelOrder.append("%s:%d" % (tHandle,tEntry[0]))
|
|
||||||
return True
|
|
||||||
|
|
||||||
def buildReferenceList(self, tHandle):
|
return theStructure
|
||||||
|
|
||||||
|
def getCounts(self, tHandle, sTitle=None):
|
||||||
|
"""Returns the counts for a file, or a section of a file
|
||||||
|
starting at title nTitle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cC = 0
|
||||||
|
wC = 0
|
||||||
|
pC = 0
|
||||||
|
|
||||||
|
if sTitle is None:
|
||||||
|
if tHandle in self.textCounts:
|
||||||
|
cC = self.textCounts[tHandle][0]
|
||||||
|
wC = self.textCounts[tHandle][1]
|
||||||
|
pC = self.textCounts[tHandle][2]
|
||||||
|
else:
|
||||||
|
if tHandle in self.novelIndex:
|
||||||
|
if sTitle in self.novelIndex[tHandle]:
|
||||||
|
cC = self.novelIndex[tHandle][sTitle]["cCount"]
|
||||||
|
wC = self.novelIndex[tHandle][sTitle]["wCount"]
|
||||||
|
pC = self.novelIndex[tHandle][sTitle]["pCount"]
|
||||||
|
elif tHandle in self.noteIndex:
|
||||||
|
if sTitle in self.noteIndex[tHandle]:
|
||||||
|
cC = self.noteIndex[tHandle][sTitle]["cCount"]
|
||||||
|
wC = self.noteIndex[tHandle][sTitle]["wCount"]
|
||||||
|
pC = self.noteIndex[tHandle][sTitle]["pCount"]
|
||||||
|
|
||||||
|
return cC, wC, pC
|
||||||
|
|
||||||
|
def getReferences(self, tHandle, sTitle=None):
|
||||||
|
"""Extract all references made in a file, and optionally title
|
||||||
|
section. sTitle must be a string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
theRefs = {}
|
||||||
|
for tKey in self.TAG_CLASS:
|
||||||
|
theRefs[tKey] = []
|
||||||
|
|
||||||
|
if tHandle not in self.refIndex:
|
||||||
|
return theRefs
|
||||||
|
|
||||||
|
try:
|
||||||
|
for refTitle in self.refIndex[tHandle]:
|
||||||
|
for nLine, tKey, tTag in self.refIndex[tHandle][refTitle]["tags"]:
|
||||||
|
if sTitle is None or sTitle == refTitle:
|
||||||
|
theRefs[tKey].append(tTag)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to generate reference list")
|
||||||
|
logger.error(str(e))
|
||||||
|
|
||||||
|
return theRefs
|
||||||
|
|
||||||
|
def getBackReferenceList(self, tHandle):
|
||||||
"""Build a list of files referring back to our file, specified
|
"""Build a list of files referring back to our file, specified
|
||||||
by tHandle.
|
by tHandle.
|
||||||
"""
|
"""
|
||||||
@@ -418,9 +597,10 @@ class NWIndex():
|
|||||||
|
|
||||||
if theTag is not None:
|
if theTag is not None:
|
||||||
for tHandle in self.refIndex:
|
for tHandle in self.refIndex:
|
||||||
for nLine, tKey, tTag, nTitle in self.refIndex[tHandle]:
|
for sTitle in self.refIndex[tHandle]:
|
||||||
if tTag == theTag:
|
for nLine, tKey, tTag in self.refIndex[tHandle][sTitle]["tags"]:
|
||||||
theRefs[tHandle] = nLine
|
if tTag == theTag:
|
||||||
|
theRefs[tHandle] = nLine
|
||||||
|
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
@@ -433,46 +613,4 @@ class NWIndex():
|
|||||||
return theRef[1], theRef[0]
|
return theRef[1], theRef[0]
|
||||||
return None, 0
|
return None, 0
|
||||||
|
|
||||||
def buildTagNovelMap(self, theTags, theFilters=None):
|
|
||||||
"""Build a two-dimensional map of all titles of the novel and
|
|
||||||
which tags they link to from the various meta tags. This map is
|
|
||||||
used to display the timeline view.
|
|
||||||
"""
|
|
||||||
|
|
||||||
tagMap = {}
|
|
||||||
tagClass = {}
|
|
||||||
exClass = []
|
|
||||||
|
|
||||||
if theFilters is not None:
|
|
||||||
if "exClass" in theFilters.keys():
|
|
||||||
exClass = theFilters["exClass"]
|
|
||||||
|
|
||||||
for theTag in theTags:
|
|
||||||
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]:
|
|
||||||
if tTag in tagMap.keys() and tKey in self.TAG_CLASS:
|
|
||||||
try:
|
|
||||||
nPos = self.novelOrder.index("%s:%d" % (tHandle, nTitle))
|
|
||||||
if self.TAG_CLASS[tKey][0] == tagClass[tTag]:
|
|
||||||
tagMap[tTag][nPos] = self.TAG_CLASS[tKey][1]
|
|
||||||
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
|
# END Class NWIndex
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class NWProject():
|
|||||||
|
|
||||||
# Project Settings
|
# Project Settings
|
||||||
self.spellCheck = False
|
self.spellCheck = False
|
||||||
|
self.autoOutline = True
|
||||||
self.statusItems = None
|
self.statusItems = None
|
||||||
self.importItems = None
|
self.importItems = None
|
||||||
self.lastEdited = None
|
self.lastEdited = None
|
||||||
@@ -169,6 +170,7 @@ class NWProject():
|
|||||||
self.bookAuthors = []
|
self.bookAuthors = []
|
||||||
self.autoReplace = {}
|
self.autoReplace = {}
|
||||||
self.spellCheck = False
|
self.spellCheck = False
|
||||||
|
self.autoOutline = True
|
||||||
self.statusItems = NWStatus()
|
self.statusItems = NWStatus()
|
||||||
self.statusItems.addEntry("New", (100,100,100))
|
self.statusItems.addEntry("New", (100,100,100))
|
||||||
self.statusItems.addEntry("Note", (200, 50, 0))
|
self.statusItems.addEntry("Note", (200, 50, 0))
|
||||||
@@ -299,6 +301,8 @@ class NWProject():
|
|||||||
continue
|
continue
|
||||||
if xItem.tag == "spellCheck":
|
if xItem.tag == "spellCheck":
|
||||||
self.spellCheck = checkBool(xItem.text,False)
|
self.spellCheck = checkBool(xItem.text,False)
|
||||||
|
elif xItem.tag == "autoOutline":
|
||||||
|
self.autoOutline = checkBool(xItem.text,True)
|
||||||
elif xItem.tag == "lastEdited":
|
elif xItem.tag == "lastEdited":
|
||||||
self.lastEdited = checkString(xItem.text,None,True)
|
self.lastEdited = checkString(xItem.text,None,True)
|
||||||
elif xItem.tag == "lastViewed":
|
elif xItem.tag == "lastViewed":
|
||||||
@@ -390,6 +394,7 @@ class NWProject():
|
|||||||
# Save Project Settings
|
# Save Project Settings
|
||||||
xSettings = etree.SubElement(nwXML, "settings")
|
xSettings = etree.SubElement(nwXML, "settings")
|
||||||
self._saveProjectValue(xSettings, "spellCheck", self.spellCheck)
|
self._saveProjectValue(xSettings, "spellCheck", self.spellCheck)
|
||||||
|
self._saveProjectValue(xSettings, "autoOutline", self.autoOutline)
|
||||||
self._saveProjectValue(xSettings, "lastEdited", self.lastEdited)
|
self._saveProjectValue(xSettings, "lastEdited", self.lastEdited)
|
||||||
self._saveProjectValue(xSettings, "lastViewed", self.lastViewed)
|
self._saveProjectValue(xSettings, "lastViewed", self.lastViewed)
|
||||||
self._saveProjectValue(xSettings, "lastWordCount", self.currWCount)
|
self._saveProjectValue(xSettings, "lastWordCount", self.currWCount)
|
||||||
@@ -513,6 +518,12 @@ class NWProject():
|
|||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setAutoOutline(self, theMode):
|
||||||
|
if self.autoOutline != theMode:
|
||||||
|
self.autoOutline = theMode
|
||||||
|
self.setProjectChanged(True)
|
||||||
|
return True
|
||||||
|
|
||||||
def setTreeOrder(self, newOrder):
|
def setTreeOrder(self, newOrder):
|
||||||
if len(self.treeOrder) != len(newOrder):
|
if len(self.treeOrder) != len(newOrder):
|
||||||
logger.warning("Size of new and old tree order does not match")
|
logger.warning("Size of new and old tree order does not match")
|
||||||
|
|||||||
@@ -81,6 +81,18 @@ class OptionState():
|
|||||||
self.theState[setGroup][setName] = setValue
|
self.theState[setGroup][setName] = setValue
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getValue(self, getGroup, getName, defaultValue):
|
||||||
|
"""Return an arbitrary type value, if it exists. Otherwise,
|
||||||
|
return the default value.
|
||||||
|
"""
|
||||||
|
if getGroup in self.theState:
|
||||||
|
if getName in self.theState[getGroup]:
|
||||||
|
try:
|
||||||
|
return self.theState[getGroup][getName]
|
||||||
|
except:
|
||||||
|
return defaultValue
|
||||||
|
return defaultValue
|
||||||
|
|
||||||
def getString(self, getGroup, getName, defaultValue):
|
def getString(self, getGroup, getName, defaultValue):
|
||||||
"""Return the value as a string, if it exists. Otherwise, return
|
"""Return the value as a string, if it exists. Otherwise, return
|
||||||
the default value.
|
the default value.
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
@pov: Jane
|
@pov: Jane
|
||||||
@location: Earth
|
@location: Earth
|
||||||
|
|
||||||
% We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish.
|
%synopsis: We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish to.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" timeStamp="2020-02-19 19:55:13">
|
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" saveCount="31" autoCount="5" timeStamp="2020-04-13 15:55:18">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
@@ -9,9 +9,10 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>True</spellCheck>
|
<spellCheck>True</spellCheck>
|
||||||
<lastEdited>636b6aa9b697b</lastEdited>
|
<autoOutline>True</autoOutline>
|
||||||
|
<lastEdited>6a2d6d5f4f401</lastEdited>
|
||||||
<lastViewed>b3e74dbc1f584</lastViewed>
|
<lastViewed>b3e74dbc1f584</lastViewed>
|
||||||
<lastWordCount>875</lastWordCount>
|
<lastWordCount>869</lastWordCount>
|
||||||
<autoReplace>
|
<autoReplace>
|
||||||
<A>B</A>
|
<A>B</A>
|
||||||
<B>E</B>
|
<B>E</B>
|
||||||
@@ -70,7 +71,7 @@
|
|||||||
<charCount>12</charCount>
|
<charCount>12</charCount>
|
||||||
<wordCount>3</wordCount>
|
<wordCount>3</wordCount>
|
||||||
<paraCount>0</paraCount>
|
<paraCount>0</paraCount>
|
||||||
<cursorPos>45</cursorPos>
|
<cursorPos>15</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
|
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
|
||||||
<name>Making a Scene</name>
|
<name>Making a Scene</name>
|
||||||
@@ -239,9 +240,9 @@
|
|||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>30</charCount>
|
<charCount>0</charCount>
|
||||||
<wordCount>6</wordCount>
|
<wordCount>0</wordCount>
|
||||||
<paraCount>1</paraCount>
|
<paraCount>0</paraCount>
|
||||||
<cursorPos>36</cursorPos>
|
<cursorPos>36</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>False</spellCheck>
|
<spellCheck>False</spellCheck>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>None</lastEdited>
|
<lastEdited>None</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>0</lastWordCount>
|
<lastWordCount>0</lastWordCount>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>True</spellCheck>
|
<spellCheck>True</spellCheck>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>31489056e0916</lastEdited>
|
<lastEdited>31489056e0916</lastEdited>
|
||||||
<lastViewed>31489056e0916</lastViewed>
|
<lastViewed>31489056e0916</lastViewed>
|
||||||
<lastWordCount>86</lastWordCount>
|
<lastWordCount>86</lastWordCount>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{"tagIndex": {"Jane": [3, "2fca346db6561", "CHARACTER"], "MainPlot": [3, "02d20bbd7e394", "PLOT"], "Home": [3, "7688b6ef52555", "WORLD"]}, "refIndex": {"31489056e0916": [[5, "@pov", "Jane", 3], [6, "@plot", "MainPlot", 3], [11, "@pov", "Jane", 8], [12, "@plot", "MainPlot", 8], [13, "@location", "Home", 8], [17, "@char", "Jane", 15]], "2fca346db6561": [], "02d20bbd7e394": [], "7688b6ef52555": []}, "novelIndex": {"31489056e0916": [[1, 1, "Novel", "SCENE"], [3, 2, "Chapter", "SCENE"], [8, 3, "Scene", "SCENE"], [15, 4, "Some Section", "SCENE"]]}, "noteIndex": {"2fca346db6561": [[1, 1, "Jane Doe", "NOTE"]], "02d20bbd7e394": [[1, 1, "Main Plot", "NOTE"]], "7688b6ef52555": [[1, 1, "Main Location", "NOTE"]]}}
|
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>False</spellCheck>
|
<spellCheck>False</spellCheck>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>None</lastEdited>
|
<lastEdited>None</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>0</lastWordCount>
|
<lastWordCount>0</lastWordCount>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>False</spellCheck>
|
<spellCheck>False</spellCheck>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>None</lastEdited>
|
<lastEdited>None</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>0</lastWordCount>
|
<lastWordCount>0</lastWordCount>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>False</spellCheck>
|
<spellCheck>False</spellCheck>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>None</lastEdited>
|
<lastEdited>None</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>0</lastWordCount>
|
<lastWordCount>0</lastWordCount>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>False</spellCheck>
|
<spellCheck>False</spellCheck>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>None</lastEdited>
|
<lastEdited>None</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>0</lastWordCount>
|
<lastWordCount>0</lastWordCount>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from os import path, unlink
|
|||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||||
from nw.gui.dialogs.timelineview import GuiTimeLineView
|
|
||||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||||
|
|
||||||
from nw.constants import *
|
from nw.constants import *
|
||||||
@@ -246,35 +245,10 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
|
|||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
|
||||||
refFile = path.join(nwTempGUI,"data_7","688b6ef52555_main.nwd")
|
refFile = path.join(nwTempGUI,"data_7","688b6ef52555_main.nwd")
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_688b6ef52555_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_688b6ef52555_main.nwd"))
|
||||||
if sys.version_info[0] >= 3 and sys.version_info[1] >= 6:
|
|
||||||
refFile = path.join(nwTempGUI,"meta","tagsIndex.json")
|
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_tagsIndex.json"))
|
|
||||||
|
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
|
|
||||||
@pytest.mark.gui
|
|
||||||
def testTimeLineView(qtbot, nwTempGUI, nwRef, nwTemp):
|
|
||||||
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
|
|
||||||
qtbot.addWidget(nwGUI)
|
|
||||||
nwGUI.show()
|
|
||||||
qtbot.waitForWindowShown(nwGUI)
|
|
||||||
qtbot.wait(stepDelay)
|
|
||||||
|
|
||||||
# Create new, save, open project
|
|
||||||
nwGUI.theProject.handleSeed = 42
|
|
||||||
assert nwGUI.openProject(nwTempGUI)
|
|
||||||
qtbot.wait(stepDelay)
|
|
||||||
|
|
||||||
timeLine = GuiTimeLineView(nwGUI, nwGUI.theProject, nwGUI.theIndex)
|
|
||||||
qtbot.addWidget(timeLine)
|
|
||||||
|
|
||||||
assert timeLine.numRows == 4
|
|
||||||
assert timeLine.numCols == 3
|
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
|
||||||
nwGUI.closeMain()
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
|
def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
|
||||||
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
|
||||||
|
|||||||
@@ -111,26 +111,3 @@ def testIndexScanThis(nwTempProj):
|
|||||||
assert str(thePos) == "[0, 6, 12]"
|
assert str(thePos) == "[0, 6, 12]"
|
||||||
|
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|
||||||
@pytest.mark.project
|
|
||||||
def testBuildIndex(nwTempProj):
|
|
||||||
projFile = path.join(nwTempProj,"nwProject.nwx")
|
|
||||||
assert theProject.openProject(projFile)
|
|
||||||
|
|
||||||
theIndex = NWIndex(theProject,theMain)
|
|
||||||
tHandle = "31489056e0916"
|
|
||||||
|
|
||||||
theIndex.scanText(tHandle, (
|
|
||||||
"# Novel\n\n"
|
|
||||||
"## Chapter\n\n"
|
|
||||||
"### Scene\n\n"
|
|
||||||
"#### Section\n\n"
|
|
||||||
"@pov: John\n"
|
|
||||||
"@char: Jane\n"
|
|
||||||
"@location: Somewhere\n"
|
|
||||||
))
|
|
||||||
|
|
||||||
assert theIndex.buildNovelList()
|
|
||||||
assert str(theIndex.novelList) == "[[1, 1, 'Novel', 'SCENE'], [3, 2, 'Chapter', 'SCENE'], [5, 3, 'Scene', 'SCENE'], [7, 4, 'Section', 'SCENE']]"
|
|
||||||
assert str(theIndex.novelOrder) == "['31489056e0916:1', '31489056e0916:3', '31489056e0916:5', '31489056e0916:7']"
|
|
||||||
assert theProject.closeProject()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user