diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 4c215058..680c5010 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -12,7 +12,6 @@ from nw.gui.dialogs.export import GuiExport from nw.gui.dialogs.itemeditor import GuiItemEditor from nw.gui.dialogs.projecteditor import GuiProjectEditor from nw.gui.dialogs.sessionlog import GuiSessionLogView -from nw.gui.dialogs.timelineview import GuiTimeLineView # GUI Elements from nw.gui.elements.docdetails import GuiDocDetails @@ -38,7 +37,6 @@ __all__ = [ "GuiItemEditor", "GuiProjectEditor", "GuiSessionLogView", - "GuiTimeLineView", "GuiDocDetails", "GuiDocEditor", "GuiDocTree", diff --git a/nw/gui/dialogs/__init__.py b/nw/gui/dialogs/__init__.py index fac299ba..9374ff13 100644 --- a/nw/gui/dialogs/__init__.py +++ b/nw/gui/dialogs/__init__.py @@ -5,7 +5,6 @@ from nw.gui.dialogs.export import GuiExport from nw.gui.dialogs.itemeditor import GuiItemEditor from nw.gui.dialogs.projecteditor import GuiProjectEditor from nw.gui.dialogs.sessionlog import GuiSessionLogView -from nw.gui.dialogs.timelineview import GuiTimeLineView __all__ = [ "GuiConfigEditor", @@ -13,5 +12,4 @@ __all__ = [ "GuiItemEditor", "GuiProjectEditor", "GuiSessionLogView", - "GuiTimeLineView", ] diff --git a/nw/gui/dialogs/timelineview.py b/nw/gui/dialogs/timelineview.py deleted file mode 100644 index dab545fc..00000000 --- a/nw/gui/dialogs/timelineview.py +++ /dev/null @@ -1,273 +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 os import path - -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 -from nw.tools import OptLastState - -logger = logging.getLogger(__name__) - -class GuiTimeLineView(QDialog): - - def __init__(self, theParent, theProject, theIndex): - QDialog.__init__(self, theParent) - - logger.debug("Initialising TimeLineView ...") - - self.mainConf = nw.CONFIG - self.theProject = theProject - self.theParent = theParent - self.theIndex = theIndex - self.optState = TimeLineLastState(self.theProject,nwFiles.TLINE_OPT) - self.optState.loadSettings() - - self.theMatrix = {} - self.numRows = 0 - self.numCols = 0 - - self.outerBox = QVBoxLayout() - self.filterBox = QVBoxLayout() - self.centreBox = QHBoxLayout() - self.bottomBox = QHBoxLayout() - - self.setWindowTitle("Timeline View") - self.setMinimumWidth(700) - self.setMinimumHeight(400) - - winWidth = self.optState.validIntRange( - self.optState.getSetting("winWidth"), 700, 10000, 700 - ) - winHeight = self.optState.validIntRange( - self.optState.getSetting("winHeight"), 400, 10000, 400 - ) - self.resize(winWidth,winHeight) - - # TimeLine Table - self.mainTable = QTableWidget() - self.mainTable.setGridStyle(Qt.NoPen) - - self.hHeader = self.mainTable.horizontalHeader() - self.hHeader.setSectionResizeMode(QHeaderView.ResizeToContents) - self.mainTable.setHorizontalHeader(self.hHeader) - - self.vHeader = self.mainTable.verticalHeader() - self.vHeader.setSectionResizeMode(QHeaderView.ResizeToContents) - self.mainTable.setVerticalHeader(self.vHeader) - - # Option Box - self.optFilter = QGroupBox("Include Tags", self) - self.optFilterGrid = QGridLayout(self) - self.optFilter.setLayout(self.optFilterGrid) - - self.filterPlot = QCheckBox("Plot tags", self) - self.filterPlot.setChecked(self.optState.getSetting("fPlot")) - self.filterPlot.stateChanged.connect(self._filterChange) - - self.filterChar = QCheckBox("Character tags", self) - self.filterChar.setChecked(self.optState.getSetting("fChar")) - self.filterChar.stateChanged.connect(self._filterChange) - - self.filterWorld = QCheckBox("Location tags", self) - self.filterWorld.setChecked(self.optState.getSetting("fWorld")) - self.filterWorld.stateChanged.connect(self._filterChange) - - self.filterTime = QCheckBox("Timeline tags", self) - self.filterTime.setChecked(self.optState.getSetting("fTime")) - self.filterTime.stateChanged.connect(self._filterChange) - - self.filterObject = QCheckBox("Object tags", self) - self.filterObject.setChecked(self.optState.getSetting("fObject")) - self.filterObject.stateChanged.connect(self._filterChange) - - self.filterCustom = QCheckBox("Custom tags", self) - self.filterCustom.setChecked(self.optState.getSetting("fCustom")) - self.filterCustom.stateChanged.connect(self._filterChange) - - self.optFilterGrid.addWidget(self.filterPlot, 0, 1) - self.optFilterGrid.addWidget(self.filterChar, 1, 1) - self.optFilterGrid.addWidget(self.filterWorld, 2, 1) - self.optFilterGrid.addWidget(self.filterTime, 3, 1) - self.optFilterGrid.addWidget(self.filterObject, 4, 1) - self.optFilterGrid.addWidget(self.filterCustom, 5, 1) - - self.optHide = QGroupBox("Filters", self) - self.optHideGrid = QGridLayout(self) - self.optHide.setLayout(self.optHideGrid) - - self.hideUnused = QCheckBox("Hide unused", self) - self.hideUnused.setChecked(self.optState.getSetting("hUnused")) - self.hideUnused.stateChanged.connect(self._filterChange) - - self.optHideGrid.addWidget(self.hideUnused, 0, 1) - - # Button Box - self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) - self.buttonBox.rejected.connect(self._doClose) - - self.btnRebuild = QPushButton("Rebuild Index") - self.btnRebuild.clicked.connect(self.theParent.rebuildIndex) - - self.btnRefresh = QPushButton("Refresh Table") - self.btnRefresh.clicked.connect(self._buildNovelList) - - self.bottomBox.addWidget(self.btnRebuild) - self.bottomBox.addWidget(self.btnRefresh) - self.bottomBox.addStretch() - self.bottomBox.addWidget(self.buttonBox) - - # Assemble - self.filterBox.addWidget(self.optFilter) - self.filterBox.addWidget(self.optHide) - self.filterBox.addStretch() - self.centreBox.addWidget(self.mainTable) - self.centreBox.addLayout(self.filterBox) - self.outerBox.addLayout(self.centreBox) - self.outerBox.addLayout(self.bottomBox) - self.setLayout(self.outerBox) - - self._buildNovelList() - self.buttonBox.setFocus() - - self.show() - - logger.debug("TimeLineView initialisation complete") - - return - - def _buildNovelList(self): - - self.mainTable.clear() - self.theIndex.buildNovelList() - - self.numRows = len(self.theIndex.novelList) - self.mainTable.setRowCount(self.numRows) - - theFilters = {} - theFilters["exClass"] = [] - theFilters["hUnused"] = self.hideUnused.isChecked() - - if not self.filterPlot.isChecked(): - theFilters["exClass"].append(nwItemClass.PLOT) - if not self.filterChar.isChecked(): - theFilters["exClass"].append(nwItemClass.CHARACTER) - if not self.filterWorld.isChecked(): - theFilters["exClass"].append(nwItemClass.WORLD) - if not self.filterTime.isChecked(): - theFilters["exClass"].append(nwItemClass.TIMELINE) - if not self.filterObject.isChecked(): - theFilters["exClass"].append(nwItemClass.OBJECT) - if not self.filterCustom.isChecked(): - theFilters["exClass"].append(nwItemClass.CUSTOM) - - for n in range(len(self.theIndex.novelList)): - iDepth = self.theIndex.novelList[n][1] - iTitle = self.theIndex.novelList[n][2] - newItem = QTableWidgetItem("%s%s " % (" "*iDepth,iTitle)) - self.mainTable.setVerticalHeaderItem(n, newItem) - - theMap = self.theIndex.buildTagNovelMap(self.theIndex.tagIndex.keys(), theFilters) - self.numCols = len(theMap.keys()) - self.mainTable.setColumnCount(self.numCols) - - nCol = 0 - for theTag, theCols in theMap.items(): - newItem = QTableWidgetItem(" %s " % theTag) - self.mainTable.setHorizontalHeaderItem(nCol, newItem) - for n in range(len(theCols)): - if theCols[n] == 1: - pxNew = QPixmap(10,10) - pxNew.fill(QColor(0,120,0)) - lblNew = QLabel() - lblNew.setPixmap(pxNew) - lblNew.setAlignment(Qt.AlignCenter) - lblNew.setAttribute(Qt.WA_TranslucentBackground) - self.mainTable.setCellWidget(n, nCol, lblNew) - elif theCols[n] == 2: - pxNew = QPixmap(10,10) - pxNew.fill(QColor(0,0,120)) - lblNew = QLabel() - lblNew.setPixmap(pxNew) - lblNew.setAlignment(Qt.AlignCenter) - lblNew.setAttribute(Qt.WA_TranslucentBackground) - self.mainTable.setCellWidget(n, nCol, lblNew) - nCol += 1 - - return - - def _doClose(self): - - logger.verbose("GuiTimeLineView close button clicked") - - winWidth = self.width() - winHeight = self.height() - fPlot = self.filterPlot.isChecked() - fChar = self.filterChar.isChecked() - fWorld = self.filterWorld.isChecked() - fTime = self.filterTime.isChecked() - fObject = self.filterObject.isChecked() - fCustom = self.filterCustom.isChecked() - hUnused = self.hideUnused.isChecked() - - self.optState.setSetting("winWidth", winWidth) - self.optState.setSetting("winHeight",winHeight) - self.optState.setSetting("fPlot", fPlot) - self.optState.setSetting("fChar", fChar) - self.optState.setSetting("fWorld", fWorld) - self.optState.setSetting("fTime", fTime) - self.optState.setSetting("fObject", fObject) - self.optState.setSetting("fCustom", fCustom) - self.optState.setSetting("hUnused", hUnused) - - self.optState.saveSettings() - self.close() - - return - - def _filterChange(self, checkState): - self._buildNovelList() - return - -# END Class GuiTimeLineView - -class TimeLineLastState(OptLastState): - - def __init__(self, theProject, theFile): - OptLastState.__init__(self, theProject, theFile) - self.theState = { - "winWidth" : 700, - "winHeight" : 400, - "fPlot" : True, - "fChar" : True, - "fWorld" : True, - "fTime" : True, - "fObject" : True, - "fCustom" : True, - "hUnused" : True, - } - self.stringOpt = () - self.boolOpt = ("fPlot","fChar","fWorld","fTime","fObject","fCustom","hUnused") - self.intOpt = ("winWidth","winHeight") - return - -# END Class TimeLineLastState diff --git a/nw/gui/elements/viewdetails.py b/nw/gui/elements/viewdetails.py index 2a0c9a29..b92edd01 100644 --- a/nw/gui/elements/viewdetails.py +++ b/nw/gui/elements/viewdetails.py @@ -87,7 +87,7 @@ class GuiDocViewDetails(QWidget): if self.isSticky.isChecked(): return - theRefs = self.theParent.theIndex.buildReferenceList(tHandle) + theRefs = self.theParent.theIndex.getBackReferenceList(tHandle) theList = [] for tHandle in theRefs: tItem = self.theProject.getItem(tHandle) diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 179a6077..0c5ac3f4 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -416,16 +416,6 @@ class GuiMainMenu(QMenuBar): self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode) 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 def _buildEditMenu(self): diff --git a/nw/guimain.py b/nw/guimain.py index 74f41f91..177983bf 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -27,7 +27,7 @@ from nw.gui import ( GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails, GuiConfigEditor, GuiProjectEditor, GuiExport, - GuiItemEditor, GuiTimeLineView, GuiSessionLogView, GuiProjectOutline + GuiItemEditor, GuiSessionLogView, GuiProjectOutline ) from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup from nw.tools import countWords @@ -619,12 +619,6 @@ class GuiMain(QMainWindow): dlgExport.exec_() return True - def showTimeLineDialog(self): - if self.hasProject: - dlgTLine = GuiTimeLineView(self, self.theProject, self.theIndex) - dlgTLine.exec_() - return True - def showSessionLogDialog(self): if self.hasProject: dlgTLine = GuiSessionLogView(self, self.theProject) @@ -778,7 +772,6 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aFileDetails) self.addAction(self.mainMenu.aZenMode) self.addAction(self.mainMenu.aFullScreen) - self.addAction(self.mainMenu.aViewTimeLine) self.addAction(self.mainMenu.aEditUndo) self.addAction(self.mainMenu.aEditRedo) self.addAction(self.mainMenu.aEditCut) diff --git a/nw/project/index.py b/nw/project/index.py index 0378adc2..f0e93fbc 100644 --- a/nw/project/index.py +++ b/nw/project/index.py @@ -17,7 +17,7 @@ import nw from os import path from nw.constants import ( - nwFiles, nwKeyWords, nwItemType, nwItemClass, nwAlert + nwFiles, nwKeyWords, nwItemType, nwItemClass, nwItemLayout, nwAlert ) from nw.tools import countWords @@ -66,9 +66,6 @@ class NWIndex(): self.textCounts = {} self.textSynopsis = {} - # Lists - self.novelList = [] - return ## @@ -261,23 +258,28 @@ class NWIndex(): """ theItem = self.theProject.getItem(tHandle) - if theItem is None: return False - if theItem.itemType != nwItemType.FILE: return False - if theItem.parHandle == self.theProject.trashRoot: return False + if theItem is None: + 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 itemLayout = theItem.itemLayout logger.debug("Indexing item with handle %s" % tHandle) # Check file type, and reset its old index - if itemClass == nwItemClass.NOVEL: - self.novelIndex[tHandle] = [] - self.refIndex[tHandle] = [] - isNovel = True - else: + self.refIndex[tHandle] = [] + if itemLayout == nwItemLayout.NOTE: self.noteIndex[tHandle] = [] - self.refIndex[tHandle] = [] isNovel = False + else: + self.novelIndex[tHandle] = [] + isNovel = True # Also clear references to file in tag index clearTags = [] @@ -296,7 +298,8 @@ class NWIndex(): aLine = aLine nLine += 1 nChar = len(aLine.strip()) - if nChar == 0: continue + if nChar == 0: + continue if aLine.startswith(r"#"): isTitle = self.indexTitle(tHandle, isNovel, aLine, nLine, itemLayout) @@ -513,20 +516,7 @@ class NWIndex(): return theRefs - def buildNovelList(self): - """Build a list of the content of the novel. - """ - self.novelList = [] - self.novelOrder = [] - for tHandle in self.theProject.treeOrder: - if tHandle not in self.novelIndex: - continue - for tEntry in self.novelIndex[tHandle]: - self.novelList.append(tEntry) - self.novelOrder.append("%s:%d" % (tHandle,tEntry[0])) - return True - - def buildReferenceList(self, tHandle): + def getBackReferenceList(self, tHandle): """Build a list of files referring back to our file, specified by tHandle. """ @@ -560,46 +550,4 @@ class NWIndex(): return theRef[1], theRef[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 diff --git a/tests/test_project.py b/tests/test_project.py index 907d0e9f..41b5246a 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -95,25 +95,3 @@ def testIndexScanThis(nwTempProj): assert isValid assert str(theBits) == "['@tag', 'this', 'and this']" assert str(thePos) == "[0, 6, 12]" - -@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']"