diff --git a/nw/gui/noveltree.py b/nw/gui/noveltree.py index 35dd6632..d44d2bc0 100644 --- a/nw/gui/noveltree.py +++ b/nw/gui/noveltree.py @@ -28,8 +28,12 @@ import nw import logging +from time import time + from PyQt5.QtCore import Qt, QSize -from PyQt5.QtWidgets import QTreeWidget +from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView + +from nw.constants import nwKeyWords logger = logging.getLogger(__name__) @@ -37,7 +41,7 @@ class GuiNovelTree(QTreeWidget): C_TITLE = 0 C_WORDS = 1 - C_PAGES = 2 + C_POV = 2 def __init__(self, theParent): QTreeWidget.__init__(self, theParent) @@ -50,13 +54,28 @@ class GuiNovelTree(QTreeWidget): self.theProject = theParent.theProject self.theIndex = theParent.theIndex + # Tree State + self.lastBuild = 0 + self.treeMap = {} + # Build GUI iPx = self.theTheme.baseIconSize self.setIconSize(QSize(iPx, iPx)) - self.setExpandsOnDoubleClick(True) self.setIndentation(iPx) self.setColumnCount(3) - self.setHeaderLabels(["Title", "Words", "Pages"]) + self.setHeaderLabels(["Title", "Words", "POV"]) + self.itemDoubleClicked.connect(self._treeDoubleClick) + self.itemSelectionChanged.connect(self._itemSelected) + self.setSelectionBehavior(QAbstractItemView.SelectRows) + self.setSelectionMode(QAbstractItemView.SingleSelection) + self.setExpandsOnDoubleClick(False) + self.setDragEnabled(False) + + treeHeadItem = self.headerItem() + treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) + treeHeadItem.setToolTip(self.C_TITLE, "Section title") + treeHeadItem.setToolTip(self.C_WORDS, "Word count") + treeHeadItem.setToolTip(self.C_POV, "Point-of-view character") # Get user's column width preferences for NAME and COUNT treeColWidth = self.mainConf.getNovelColWidths() @@ -65,7 +84,7 @@ class GuiNovelTree(QTreeWidget): self.setColumnWidth(colN, colW) # The last column should just auto-scale - self.resizeColumnToContents(self.C_PAGES) + self.resizeColumnToContents(self.C_POV) # Set custom settings self.initTree() @@ -101,14 +120,21 @@ class GuiNovelTree(QTreeWidget): """Clear the GUI content and the related maps. """ self.clear() + self.treeMap = {} + return + + def refreshTree(self, overRide=False): + """Called whenever the Novel tab is activated. + """ + self._populateTree() return def getColumnSizes(self): """Return the column widths for the tree columns. """ retVals = [ - self.columnWidth(self.C_TITLE), - self.columnWidth(self.C_WORDS), + self.columnWidth(0), + self.columnWidth(1), ] return retVals @@ -116,4 +142,116 @@ class GuiNovelTree(QTreeWidget): # Slots ## + def _treeDoubleClick(self, tItem, tCol): + """Extract the handle and line number of the title double- + clicked, and send it to the main gui class for opening in the + document editor. + """ + theData = tItem.data(self.C_TITLE, Qt.UserRole) + tHandle = theData[0] + try: + tLine = int(theData[1]) + except Exception: + tLine = 1 + + logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine)) + self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True) + + return + + def _itemSelected(self): + """Extract the handle and line number of the currently selected + title, and send it to the tree meta panel. + """ + selItems = self.selectedItems() + if selItems: + tHandle = selItems[0].data(self.C_TITLE, Qt.UserRole)[0] + self.theParent.treeMeta.updateViewBox(tHandle) + + return + + ## + # Internal Functions + ## + + def _populateTree(self): + """Build the tree based on the project index. + """ + self.clear() + + for titleKey in self.theIndex.getNovelStructure(skipExcluded=True): + + 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"] + tItem = self._createTreeItem(tHandle, sTitle, tLevel) + 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, tLevel): + """Populate a tree item with all the column values. + """ + novIdx = self.theIndex.novelIndex[tHandle][sTitle] + + newItem = QTreeWidgetItem() + hIcon = "doc_%s" % tLevel.lower() + theData = (tHandle, sTitle[1:].lstrip("0")) + + wC = int(novIdx["wCount"]) + + newItem.setText(self.C_TITLE, novIdx["title"]) + newItem.setData(self.C_TITLE, Qt.UserRole, theData) + newItem.setIcon(self.C_TITLE, self.theTheme.getIcon(hIcon)) + newItem.setText(self.C_WORDS, f"{wC:n}") + newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) + + theRefs = self.theIndex.getReferences(tHandle, sTitle) + newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY])) + + return newItem + # END Class GuiNovelTree diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py index e02fece6..dd07468c 100644 --- a/nw/gui/projsettings.py +++ b/nw/gui/projsettings.py @@ -118,7 +118,7 @@ class GuiProjectSettings(PagedDialog): self.theProject.setImportColours(importCol) if self.tabStatus.colChanged or self.tabImport.colChanged: - self.theParent.rebuildTree() + self.theParent.rebuildTrees() if self.tabReplace.arChanged: newList = self.tabReplace.getNewList() diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index d9a03c04..2c52c4cb 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -357,9 +357,9 @@ class GuiProjectTree(QTreeWidget): """Return the column widths for the tree columns. """ retVals = [ - self.columnWidth(self.C_NAME), - self.columnWidth(self.C_COUNT), - self.columnWidth(self.C_EXPORT), + self.columnWidth(0), + self.columnWidth(1), + self.columnWidth(2), ] return retVals diff --git a/nw/guimain.py b/nw/guimain.py index 82a7310a..e807a591 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -190,7 +190,7 @@ class GuiMain(QMainWindow): # Initialise the Project Tree self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) - self.rebuildTree() + self.rebuildTrees() # Set Main Window Elements self.setMenuBar(self.mainMenu) @@ -312,7 +312,7 @@ class GuiMain(QMainWindow): logger.info("Creating new project") if self.theProject.newProject(projData): - self.rebuildTree() + self.rebuildTrees() self.saveProject() self.hasProject = True self.statusBar.setRefTime(self.theProject.projOpened) @@ -436,7 +436,7 @@ class GuiMain(QMainWindow): # Update GUI self._setWindowTitle(self.theProject.projName) - self.rebuildTree() + self.rebuildTrees() self.docEditor.setDictionaries() self.docEditor.setSpellCheck(self.theProject.spellCheck) self.mainMenu.setAutoOutline(self.theProject.autoOutline) @@ -746,13 +746,15 @@ class GuiMain(QMainWindow): return - def rebuildTree(self): + def rebuildTrees(self): """Rebuild the project tree. """ self._makeStatusIcons() self._makeImportIcons() self.treeView.clearTree() self.treeView.buildTree() + self.novelView.clearTree() + self.novelView.refreshTree() return def rebuildIndex(self, beQuiet=False):