From 32c6d80efbe9896d6cd255500479ff34791ddea9 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 10 Mar 2020 23:18:58 +0100 Subject: [PATCH] Restructured the way column state is preserved in the Outline tree --- nw/constants/__init__.py | 2 +- nw/gui/elements/outline.py | 235 +++++++++++++++++++++---------------- 2 files changed, 134 insertions(+), 103 deletions(-) diff --git a/nw/constants/__init__.py b/nw/constants/__init__.py index 27c14997..769c50d5 100644 --- a/nw/constants/__init__.py +++ b/nw/constants/__init__.py @@ -22,4 +22,4 @@ __all__ = [ "nwItemClass", "nwItemLayout", "nwItemType", -] \ No newline at end of file +] diff --git a/nw/gui/elements/outline.py b/nw/gui/elements/outline.py index 2cffb59e..b9f209e6 100644 --- a/nw/gui/elements/outline.py +++ b/nw/gui/elements/outline.py @@ -15,10 +15,11 @@ import nw from os import path from time import time +from enum import Enum from PyQt5.QtCore import Qt, QByteArray from PyQt5.QtWidgets import ( - QWidget, QVBoxLayout, QTreeWidget, QTreeWidgetItem, + QWidget, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView ) @@ -26,44 +27,46 @@ from nw.constants import nwKeyWords, nwLabels logger = logging.getLogger(__name__) +class HCols(Enum): + + TITLE = 0 + LEVEL = 1 + LABEL = 2 + LINE = 3 + WCOUNT = 4 + CCOUNT = 5 + PCOUNT = 6 + SYNOP = 7 + POV = 8 + CHAR = 9 + PLOT = 10 + TIME = 11 + WORLD = 12 + OBJECT = 13 + ENTITY = 14 + CUSTOM = 15 + +# END Enum HCols + class GuiProjectOutline(QTreeWidget): - I_TITLE = 0 - I_LEVEL = 1 - I_LABEL = 2 - I_LINE = 3 - I_WCOUNT = 4 - I_CCOUNT = 5 - I_PCOUNT = 6 - I_SYNOP = 7 - I_POV = 8 - I_CHAR = 9 - I_PLOT = 10 - I_TIME = 11 - I_WORLD = 12 - I_OBJECT = 13 - I_ENTITY = 14 - I_CUSTOM = 15 - - COL_MAX = 15 - COL_LABELS = { - I_TITLE : "Title", - I_LEVEL : "Level", - I_LABEL : "Document", - I_LINE : "Line", - I_WCOUNT : "Words", - I_CCOUNT : "Chars", - I_PCOUNT : "Pars", - I_SYNOP : "Synopsis", - I_POV : "POV", - I_CHAR : nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY], - I_PLOT : nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY], - I_TIME : nwLabels.KEY_NAME[nwKeyWords.TIME_KEY], - I_WORLD : nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY], - I_OBJECT : nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY], - I_ENTITY : nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY], - I_CUSTOM : nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY], + HCols.TITLE : "Title", + HCols.LEVEL : "Level", + HCols.LABEL : "Document", + HCols.LINE : "Line", + HCols.WCOUNT : "Words", + HCols.CCOUNT : "Chars", + HCols.PCOUNT : "Pars", + HCols.POV : "POV", + HCols.CHAR : nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY], + HCols.PLOT : nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY], + HCols.TIME : nwLabels.KEY_NAME[nwKeyWords.TIME_KEY], + HCols.WORLD : nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY], + HCols.OBJECT : nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY], + HCols.ENTITY : nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY], + HCols.CUSTOM : nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY], + HCols.SYNOP : "Synopsis", } def __init__(self, theParent, theProject): @@ -76,6 +79,7 @@ class GuiProjectOutline(QTreeWidget): self.theProject = theProject self.theIndex = self.theParent.theIndex self.optState = self.theProject.optState + self.headerMenu = GuiOutlineHeaderMenu(self) self.firstView = True self.lastBuild = 0 @@ -86,21 +90,16 @@ class GuiProjectOutline(QTreeWidget): self.setDragEnabled(False) self.itemDoubleClicked.connect(self._treeDoubleClick) - # self.mainHead = self.header() - # self.mainHead.setContextMenuPolicy(Qt.CustomContextMenu) - # self.mainHead. + self.treeHead = self.header() + self.treeHead.setContextMenuPolicy(Qt.CustomContextMenu) + self.treeHead.customContextMenuRequested.connect(self._headerRightClick) + self.treeHead.sectionMoved.connect(self._columnMoved) self.treeMap = {} - self.treeCols = { - "order" : [ - self.I_TITLE, self.I_LABEL, - self.I_WCOUNT, self.I_POV, - self.I_CHAR, self.I_PLOT, - self.I_WORLD, self.I_SYNOP - ], - "width" : [150, 100, 80, 100, 100, 100, 100, 300], - } - self.colIndex = {} + self.treeOrder = self.COL_LABELS.keys() + self.treeNCols = len(self.treeOrder) + self.treeWidth = [150]*self.treeNCols + self.colIndex = {} logger.debug("ProjectOutline initialisation complete") @@ -123,11 +122,9 @@ class GuiProjectOutline(QTreeWidget): def closeOutline(self): """Called before a project is closed. """ - self._saveHeaderState() self.clear() self.firstView = True - return ## @@ -138,6 +135,22 @@ class GuiProjectOutline(QTreeWidget): print(tItem, tCol) return + def _headerRightClick(self, clickPos): + print(clickPos) + globPos = self.mapToGlobal(clickPos) + print(globPos) + return + + def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx): + """Make sure the order and width read from settings file, or + with default values, is kept up-to-date when columns are moved + around. Otherwise, the original order will be restored on a tree + rebuild. + """ + self.treeOrder.insert(newVisualIdx, self.treeOrder.pop(oldVisualIdx)) + self.treeWidth.insert(newVisualIdx, self.treeWidth.pop(oldVisualIdx)) + return + ## # Internal Functions ## @@ -147,20 +160,35 @@ class GuiProjectOutline(QTreeWidget): and column width. """ - treeCols = self.optState.getValue("GuiProjectOutline", "headerState", self.treeCols) + # Load whatever we saved last time, regardless of wether it + # contains the correct names or number of columns. + keysOrder = self.COL_LABELS.keys() + tempOrder = self.optState.getValue("GuiProjectOutline", "headerOrder", keysOrder) + treeOrder = [] + for hName in tempOrder: + for hItem in HCols: + if hItem.name == hName: + treeOrder.append(hItem) - if "order" not in treeCols.keys(): return - if not isinstance(treeCols["order"], list): return - if len(treeCols["order"]) == 0: return + # Add columns that were not in tempOrder to treeOrder, but in + # the default column order. + for cItem in keysOrder: + if cItem not in treeOrder: + treeOrder.append(cItem) - self.treeCols["order"] = [] - for colID in treeCols["order"]: - if colID >= 0 and colID <= self.COL_MAX: - self.treeCols["order"].append(colID) + # 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)) - if "width" in treeCols.keys(): - if isinstance(treeCols["width"],list): - self.treeCols["width"] = treeCols["width"] + # The columns widths we just fill whatever we've got, and append + # the rest with defaults, and truncate to desired length. + tempWidth = self.optState.getValue("GuiProjectOutline", "headerWidth", []) + treeWidth = [int(w) for w in tempWidth] + self.treeWidth = (treeWidth + self.treeWidth)[0:self.treeNCols] return @@ -169,12 +197,15 @@ class GuiProjectOutline(QTreeWidget): and column width. """ - colW = [] + treeWidth = [] + treeOrder = [] for iCol in range(self.columnCount()): - colW.append(self.columnWidth(iCol)) + treeOrder.append(self.treeOrder[iCol].name) + iLog = self.treeHead.logicalIndex(iCol) + treeWidth.append(self.columnWidth(iLog)) - self.treeCols["width"] = colW - self.optState.setValue("GuiProjectOutline", "headerState", self.treeCols) + self.optState.setValue("GuiProjectOutline", "headerOrder", treeOrder) + self.optState.setValue("GuiProjectOutline", "headerWidth", treeWidth) self.optState.saveSettings() return @@ -184,22 +215,19 @@ class GuiProjectOutline(QTreeWidget): """ theLabels = [] - for i, n in enumerate(self.treeCols["order"]): - theLabels.append(self.COL_LABELS[n]) - self.colIndex[n] = i + for i, hItem in enumerate(self.treeOrder): + theLabels.append(self.COL_LABELS[hItem]) + self.colIndex[hItem] = i self.clear() self.setHeaderLabels(theLabels) - for n, colW in enumerate(self.treeCols["width"]): + for n, colW in enumerate(self.treeWidth): self.setColumnWidth(n,colW) - treeHead = self.headerItem() - if self.I_CCOUNT in self.colIndex: - treeHead.setTextAlignment(self.colIndex[self.I_CCOUNT],Qt.AlignRight) - if self.I_WCOUNT in self.colIndex: - treeHead.setTextAlignment(self.colIndex[self.I_WCOUNT],Qt.AlignRight) - if self.I_PCOUNT in self.colIndex: - treeHead.setTextAlignment(self.colIndex[self.I_PCOUNT],Qt.AlignRight) + headItem = self.headerItem() + headItem.setTextAlignment(self.colIndex[HCols.CCOUNT],Qt.AlignRight) + headItem.setTextAlignment(self.colIndex[HCols.WCOUNT],Qt.AlignRight) + headItem.setTextAlignment(self.colIndex[HCols.PCOUNT],Qt.AlignRight) currTitle = None currChapter = None @@ -267,35 +295,38 @@ class GuiProjectOutline(QTreeWidget): novIdx = self.theIndex.novelIndex[tHandle][sTitle] newItem = QTreeWidgetItem() - self._setItemText(newItem, self.I_TITLE, novIdx["title"]) - self._setItemText(newItem, self.I_LEVEL, novIdx["level"]) - self._setItemText(newItem, self.I_LABEL, nwItem.itemName) - self._setItemText(newItem, self.I_LINE, sTitle[1:]) - self._setItemText(newItem, self.I_SYNOP, novIdx["synopsis"]) - self._setItemText(newItem, self.I_CCOUNT, str(novIdx["cCount"]), True) - self._setItemText(newItem, self.I_WCOUNT, str(novIdx["wCount"]), True) - self._setItemText(newItem, self.I_PCOUNT, str(novIdx["pCount"]), True) + + newItem.setText(self.colIndex[HCols.TITLE], novIdx["title"]) + newItem.setText(self.colIndex[HCols.LEVEL], novIdx["level"]) + newItem.setText(self.colIndex[HCols.LABEL], nwItem.itemName) + newItem.setText(self.colIndex[HCols.LINE], sTitle[1:]) + newItem.setText(self.colIndex[HCols.SYNOP], novIdx["synopsis"]) + newItem.setText(self.colIndex[HCols.CCOUNT], str(novIdx["cCount"])) + newItem.setText(self.colIndex[HCols.WCOUNT], str(novIdx["wCount"])) + newItem.setText(self.colIndex[HCols.PCOUNT], str(novIdx["pCount"])) + newItem.setTextAlignment(self.colIndex[HCols.CCOUNT], Qt.AlignRight) + newItem.setTextAlignment(self.colIndex[HCols.WCOUNT], Qt.AlignRight) + newItem.setTextAlignment(self.colIndex[HCols.PCOUNT], Qt.AlignRight) theRefs = self.theIndex.getReferences(tHandle, sTitle) - self._setItemText(newItem, self.I_POV, ", ".join(theRefs[nwKeyWords.POV_KEY])) - self._setItemText(newItem, self.I_CHAR, ", ".join(theRefs[nwKeyWords.CHAR_KEY])) - self._setItemText(newItem, self.I_PLOT, ", ".join(theRefs[nwKeyWords.PLOT_KEY])) - self._setItemText(newItem, self.I_TIME, ", ".join(theRefs[nwKeyWords.TIME_KEY])) - self._setItemText(newItem, self.I_WORLD, ", ".join(theRefs[nwKeyWords.WORLD_KEY])) - self._setItemText(newItem, self.I_OBJECT, ", ".join(theRefs[nwKeyWords.OBJECT_KEY])) - self._setItemText(newItem, self.I_ENTITY, ", ".join(theRefs[nwKeyWords.ENTITY_KEY])) - self._setItemText(newItem, self.I_CUSTOM, ", ".join(theRefs[nwKeyWords.CUSTOM_KEY])) + newItem.setText(self.colIndex[HCols.POV], ", ".join(theRefs[nwKeyWords.POV_KEY])) + newItem.setText(self.colIndex[HCols.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY])) + newItem.setText(self.colIndex[HCols.PLOT], ", ".join(theRefs[nwKeyWords.PLOT_KEY])) + newItem.setText(self.colIndex[HCols.TIME], ", ".join(theRefs[nwKeyWords.TIME_KEY])) + newItem.setText(self.colIndex[HCols.WORLD], ", ".join(theRefs[nwKeyWords.WORLD_KEY])) + newItem.setText(self.colIndex[HCols.OBJECT], ", ".join(theRefs[nwKeyWords.OBJECT_KEY])) + newItem.setText(self.colIndex[HCols.ENTITY], ", ".join(theRefs[nwKeyWords.ENTITY_KEY])) + newItem.setText(self.colIndex[HCols.CUSTOM], ", ".join(theRefs[nwKeyWords.CUSTOM_KEY])) return newItem - def _setItemText(self, tItem, colID, theText, rAlign=False): - """Set the correct text in the correct column, and if necessary, - right align it. - """ - if colID in self.colIndex: - tItem.setText(self.colIndex[colID], theText) - if rAlign: - tItem.setTextAlignment(self.colIndex[colID], Qt.AlignRight) +# END Class GuiProjectOutline + +class GuiOutlineHeaderMenu(QMenu): + + def __init__(self, theParent): + QMenu.__init__(self, theParent) + return -# END Class GuiProjectOutline +# END Class GuiOutlineHeaderMenu