From b3eb276437dcee8bbac9ec868d42a10283e64ad3 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jun 2022 15:45:39 +0200 Subject: [PATCH 01/10] Wrap the novel tree in an outer widget --- novelwriter/gui/__init__.py | 4 +- novelwriter/gui/noveltree.py | 93 +++++++++++++++++++++++----- novelwriter/guimain.py | 11 ++-- tests/test_gui/test_gui_guimain.py | 8 +-- tests/test_gui/test_gui_noveltree.py | 77 +++++++++++------------ 5 files changed, 126 insertions(+), 67 deletions(-) diff --git a/novelwriter/gui/__init__.py b/novelwriter/gui/__init__.py index 1892e83d..17699942 100644 --- a/novelwriter/gui/__init__.py +++ b/novelwriter/gui/__init__.py @@ -23,7 +23,7 @@ from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.docviewer import GuiDocViewer, GuiDocViewDetails from novelwriter.gui.itemdetails import GuiItemDetails from novelwriter.gui.mainmenu import GuiMainMenu -from novelwriter.gui.noveltree import GuiNovelTree +from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.outline import GuiOutlineView from novelwriter.gui.projtree import GuiProjectView from novelwriter.gui.statusbar import GuiMainStatus @@ -37,7 +37,7 @@ __all__ = [ "GuiItemDetails", "GuiMainMenu", "GuiMainStatus", - "GuiNovelTree", + "GuiNovelView", "GuiOutlineView", "GuiProjectView", "GuiTheme", diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index f634ce23..85773478 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -30,7 +30,8 @@ from time import time from PyQt5.QtCore import Qt, QSize from PyQt5.QtWidgets import ( - QTreeWidget, QTreeWidgetItem, QAbstractItemView, QFrame + QAbstractItemView, QFrame, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget ) from novelwriter.common import checkInt @@ -39,21 +40,88 @@ from novelwriter.constants import nwKeyWords logger = logging.getLogger(__name__) +class GuiNovelView(QWidget): + + def __init__(self, mainGui): + QWidget.__init__(self, mainGui) + + self.mainGui = mainGui + + # Build GUI + self.novelTree = GuiNovelTree(self) + self.novelBar = GuiNovelToolBar(self) + + # Assemble + self.outerBox = QVBoxLayout() + self.outerBox.addWidget(self.novelBar, 0) + self.outerBox.addWidget(self.novelTree, 1) + self.outerBox.setContentsMargins(0, 0, 0, 0) + self.outerBox.setSpacing(0) + + self.setLayout(self.outerBox) + + # Function Mappings + self.refreshTree = self.novelTree.refreshTree + self.updateWordCounts = self.novelTree.updateWordCounts + self.getSelectedHandle = self.novelTree.getSelectedHandle + + return + + ## + # Methods + ## + + def initSettings(self): + self.novelTree.initSettings() + return + + def clearProject(self): + self.novelTree.clearTree() + return + + def setFocus(self): + """Forward the set focus call to the tree widget. + """ + self.novelTree.setFocus() + return + + def treeFocus(self): + """Check if the novel tree has focus. + """ + return self.novelTree.hasFocus() + +# END Class GuiNovelView + + +class GuiNovelToolBar(QWidget): + + def __init__(self, novelView): + QTreeWidget.__init__(self, novelView) + + self.mainConf = novelwriter.CONFIG + self.novelView = novelView + + return + +# END Class GuiNovelToolBar + + class GuiNovelTree(QTreeWidget): C_TITLE = 0 C_WORDS = 1 C_POV = 2 - def __init__(self, mainGui): - QTreeWidget.__init__(self, mainGui) + def __init__(self, novelView): + QTreeWidget.__init__(self, novelView) logger.debug("Initialising GuiNovelTree ...") self.mainConf = novelwriter.CONFIG - self.mainGui = mainGui - self.mainTheme = mainGui.mainTheme - self.theProject = mainGui.theProject + self.novelView = novelView + self.mainGui = novelView.mainGui + self.mainTheme = novelView.mainGui.mainTheme + self.theProject = novelView.mainGui.theProject # Internal Variables self._treeMap = {} @@ -97,13 +165,13 @@ class GuiNovelTree(QTreeWidget): self.resizeColumnToContents(self.C_POV) # Set custom settings - self.initTree() + self.initSettings() logger.debug("GuiNovelTree initialisation complete") return - def initTree(self): + def initSettings(self): """Set or update tree widget settings. """ # Scroll bars @@ -162,15 +230,6 @@ class GuiNovelTree(QTreeWidget): self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}") return - def getColumnSizes(self): - """Return the column widths for the tree columns. - """ - retVals = [ - self.columnWidth(0), - self.columnWidth(1), - ] - return retVals - def getSelectedHandle(self): """Get the currently selected handle. If multiple items are selected, return the first. diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index e15a82b0..6fa52eb6 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -40,7 +40,7 @@ from PyQt5.QtWidgets import ( from novelwriter.gui import ( GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu, - GuiMainStatus, GuiNovelTree, GuiOutlineView, GuiProjectView, GuiTheme, + GuiMainStatus, GuiNovelView, GuiOutlineView, GuiProjectView, GuiTheme, GuiViewsBar ) from novelwriter.dialogs import ( @@ -106,7 +106,7 @@ class GuiMain(QMainWindow): # Main GUI Elements self.statusBar = GuiMainStatus(self) self.projView = GuiProjectView(self) - self.novelView = GuiNovelTree(self) + self.novelView = GuiNovelView(self) self.docEditor = GuiDocEditor(self) self.viewMeta = GuiDocViewDetails(self) self.docViewer = GuiDocViewer(self) @@ -292,7 +292,7 @@ class GuiMain(QMainWindow): """ # Project Area self.projView.clearProject() - self.novelView.clearTree() + self.novelView.clearProject() self.itemDetails.clearDetails() # Work Area @@ -791,7 +791,7 @@ class GuiMain(QMainWindow): tLine = None if self.projView.treeFocus(): tHandle = self.projView.getSelectedHandle() - elif self.novelView.hasFocus(): + elif self.novelView.treeFocus(): tHandle, tLine = self.novelView.getSelectedHandle() elif self.outlineView.treeFocus(): tHandle, tLine = self.outlineView.getSelectedHandle() @@ -920,7 +920,7 @@ class GuiMain(QMainWindow): self.docEditor.initEditor() self.docViewer.initViewer() self.projView.initSettings() - self.novelView.initTree() + self.novelView.initSettings() self.outlineView.initOutline() self._updateStatusWordCount() @@ -1161,7 +1161,6 @@ class GuiMain(QMainWindow): self.mainConf.setViewPanePos(self.splitView.sizes()) self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) - self.mainConf.setNovelColWidths(self.novelView.getColumnSizes()) if not self.mainConf.isFullScreen: self.mainConf.setWinSize(self.width(), self.height()) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 5fa6951b..9a56af3d 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -28,7 +28,7 @@ from tools import cmpFiles, buildTestProject, XML_IGNORE, writeFile from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMessageBox, QInputDialog -from novelwriter.gui import GuiDocEditor, GuiNovelTree, GuiOutlineView +from novelwriter.gui import GuiDocEditor, GuiNovelView, GuiOutlineView from novelwriter.enum import nwItemType, nwWidget from novelwriter.tools import GuiProjectWizard from novelwriter.gui.projtree import GuiProjectTree @@ -134,12 +134,12 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): nwGUI.projStack.setCurrentIndex(1) nwGUI.novelView.refreshTree(True) with monkeypatch.context() as mp: - mp.setattr(GuiNovelTree, "hasFocus", lambda *a: True) + mp.setattr(GuiNovelView, "treeFocus", lambda *a: True) assert nwGUI.docEditor.docHandle() is None - actItem = nwGUI.novelView.topLevelItem(0) + actItem = nwGUI.novelView.novelTree.topLevelItem(0) chpItem = actItem.child(0) selItem = chpItem.child(0) - nwGUI.novelView.setCurrentItem(selItem) + nwGUI.novelView.novelTree.setCurrentItem(selItem) nwGUI._keyPressReturn() assert nwGUI.docEditor.docHandle() == sHandle assert nwGUI.closeDocument() is True diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index 2bf2df8a..644839d3 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -37,23 +37,24 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) nwGUI.openProject(nwMinimal) - nwTree = nwGUI.novelView + novelView = nwGUI.novelView + novelTree = novelView.novelTree ## # Show/Hide Scrollbars ## - nwTree.mainConf.hideVScroll = True - nwTree.mainConf.hideHScroll = True - nwTree.initTree() - assert not nwTree.verticalScrollBar().isVisible() - assert not nwTree.horizontalScrollBar().isVisible() + nwGUI.mainConf.hideVScroll = True + nwGUI.mainConf.hideHScroll = True + novelView.initSettings() + assert not novelTree.verticalScrollBar().isVisible() + assert not novelTree.horizontalScrollBar().isVisible() - nwTree.mainConf.hideVScroll = False - nwTree.mainConf.hideHScroll = False - nwTree.initTree() - assert nwTree.verticalScrollBar().isEnabled() - assert nwTree.horizontalScrollBar().isEnabled() + nwGUI.mainConf.hideVScroll = False + nwGUI.mainConf.hideHScroll = False + novelView.initSettings() + assert novelTree.verticalScrollBar().isEnabled() + assert novelTree.horizontalScrollBar().isEnabled() ## # Populate Tree @@ -61,31 +62,31 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView) nwGUI.rebuildIndex() - nwTree._populateTree() - assert nwTree.topLevelItemCount() == 1 + novelTree._populateTree() + assert novelTree.topLevelItemCount() == 1 # Rebuild should preserve selection - topItem = nwTree.topLevelItem(0) + topItem = novelTree.topLevelItem(0) assert not topItem.isSelected() topItem.setSelected(True) - assert nwTree.selectedItems()[0] == topItem - assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0) + assert novelTree.selectedItems()[0] == topItem + assert novelView.getSelectedHandle() == ("a35baf2e93843", 0) - nwTree.refreshTree() - assert nwTree.topLevelItem(0).isSelected() + novelView.refreshTree() + assert novelTree.topLevelItem(0).isSelected() ## # Open Items ## # Clear selection - nwTree.clearSelection() - scItem = nwTree.topLevelItem(0).child(0).child(0) + novelTree.clearSelection() + scItem = novelTree.topLevelItem(0).child(0).child(0) scItem.setSelected(True) assert scItem.isSelected() # Clear selection with mouse - vPort = nwTree.viewport() + vPort = novelTree.viewport() qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10) assert not scItem.isSelected() @@ -93,7 +94,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): scItem.setSelected(True) assert scItem.isSelected() assert nwGUI.docEditor.docHandle() is None - nwTree._treeDoubleClick(scItem, 0) + novelTree._treeDoubleClick(scItem, 0) assert nwGUI.docEditor.docHandle() == "8c659a11cd429" # Open item with middle mouse button @@ -103,13 +104,13 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10) assert nwGUI.docViewer.docHandle() is None - scRect = nwTree.visualItemRect(scItem) - oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole) - scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", "")) + scRect = novelTree.visualItemRect(scItem) + oldData = scItem.data(novelTree.C_TITLE, Qt.UserRole) + scItem.setData(novelTree.C_TITLE, Qt.UserRole, (None, "", "")) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() is None - scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData) + scItem.setData(novelTree.C_TITLE, Qt.UserRole, oldData) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() == "8c659a11cd429" @@ -132,23 +133,23 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): "#### Section\n\n" )) nwGUI.rebuildIndex() - nwTree._populateTree() - assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene" - assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter" - assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title" - assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title" + novelTree._populateTree() + assert novelTree.topLevelItem(0).text(novelTree.C_TITLE) == "Section wo/Scene" + assert novelTree.topLevelItem(1).text(novelTree.C_TITLE) == "Scene wo/Chapter" + assert novelTree.topLevelItem(2).text(novelTree.C_TITLE) == "Chapter wo/Title" + assert novelTree.topLevelItem(3).text(novelTree.C_TITLE) == "Title" - tTitle = nwTree.topLevelItem(3) - assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene" - assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter" - assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter" + tTitle = novelTree.topLevelItem(3) + assert tTitle.child(0).text(novelTree.C_TITLE) == "Section w/Title, wo/Scene" + assert tTitle.child(1).text(novelTree.C_TITLE) == "Scene w/Title, wo/Chapter" + assert tTitle.child(2).text(novelTree.C_TITLE) == "Chapter" tChap = tTitle.child(2) - assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene" - assert tChap.child(1).text(nwTree.C_TITLE) == "Scene" + assert tChap.child(0).text(novelTree.C_TITLE) == "Section w/Chapter, wo/Scene" + assert tChap.child(1).text(novelTree.C_TITLE) == "Scene" tScene = tChap.child(1) - assert tScene.child(0).text(nwTree.C_TITLE) == "Section" + assert tScene.child(0).text(novelTree.C_TITLE) == "Section" ## # Close From 52683d5a21202a6e85a15b029262027afa747889 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jun 2022 17:47:32 +0200 Subject: [PATCH 02/10] Add novel tree toolbar and connect actions --- novelwriter/core/project.py | 16 +++ novelwriter/gui/noveltree.py | 224 +++++++++++++++++++++++++++-------- novelwriter/gui/projtree.py | 26 ++-- novelwriter/guimain.py | 18 ++- sample/nwProject.nwx | 16 +-- 5 files changed, 224 insertions(+), 76 deletions(-) diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index c2683cc7..43f3297e 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -102,6 +102,8 @@ class NWProject(): self.importItems = None # Note file importance values self.lastEdited = None # The handle of the last file to be edited self.lastViewed = None # The handle of the last file to be viewed + self.lastNovel = None # The handle of the last novel root viewed + self.lastOutline = None # The handle of the last outline root viewed self.lastWCount = 0 # The project word count from last session self.lastNovelWC = 0 # The novel files word count from last session self.lastNotesWC = 0 # The note files word count from last session @@ -612,6 +614,10 @@ class NWProject(): self.lastEdited = checkString(xItem.text, None, True) elif xItem.tag == "lastViewed": self.lastViewed = checkString(xItem.text, None, True) + elif xItem.tag == "lastNovel": + self.lastNovel = checkString(xItem.text, None, True) + elif xItem.tag == "lastOutline": + self.lastOutline = checkString(xItem.text, None, True) elif xItem.tag == "lastWordCount": self.lastWCount = checkInt(xItem.text, 0, False) elif xItem.tag == "novelWordCount": @@ -732,6 +738,8 @@ class NWProject(): self._packProjectValue(xSettings, "autoOutline", self.autoOutline) self._packProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastViewed", self.lastViewed) + self._packProjectValue(xSettings, "lastNovel", self.lastNovel) + self._packProjectValue(xSettings, "lastOutline", self.lastOutline) self._packProjectValue(xSettings, "lastWordCount", self.currWCount) self._packProjectValue(xSettings, "novelWordCount", self.currNovelWC) self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC) @@ -1123,6 +1131,14 @@ class NWProject(): self.setProjectChanged(True) return True + def setLastNovelViewed(self, tHandle): + """Set last viewed novel root in the novel tree. + """ + if self.lastNovel != tHandle: + self.lastNovel = tHandle + self.setProjectChanged(True) + return True + def setStatusColours(self, newCols, delCols): """Update the list of novel file status flags. """ diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 85773478..8cec9fdb 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -27,25 +27,34 @@ import logging import novelwriter from time import time +from enum import Enum -from PyQt5.QtCore import Qt, QSize +from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal +from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import ( - QAbstractItemView, QFrame, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel, + QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) +from novelwriter.enum import nwDocMode, nwItemClass from novelwriter.common import checkInt -from novelwriter.constants import nwKeyWords +from novelwriter.constants import nwKeyWords, nwLabels logger = logging.getLogger(__name__) class GuiNovelView(QWidget): + # Signals for user interaction with the novel tree + selectedItemChanged = pyqtSignal(str) + openDocumentRequest = pyqtSignal(str, Enum, int, str) + def __init__(self, mainGui): QWidget.__init__(self, mainGui) - self.mainGui = mainGui + self.mainGui = mainGui + self.theProject = mainGui.theProject # Build GUI self.novelTree = GuiNovelTree(self) @@ -60,6 +69,11 @@ class GuiNovelView(QWidget): self.setLayout(self.outerBox) + # Connect Signals + self.novelBar.rootFolderSelectionChanged.connect( + lambda tHandle: self.novelTree.refreshTree(rootHandle=tHandle, overRide=True) + ) + # Function Mappings self.refreshTree = self.novelTree.refreshTree self.updateWordCounts = self.novelTree.updateWordCounts @@ -79,6 +93,21 @@ class GuiNovelView(QWidget): self.novelTree.clearTree() return + def openProjectTasks(self): + """Run tasks related to opening a project. + """ + lastNovel = self.theProject.lastNovel + if lastNovel is None: + lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL) + + logger.debug("Setting novel tree to root item '%s'", lastNovel) + + self.clearProject() + self.novelBar.rebuildNovelRootMenu(selHandle=lastNovel) + self.novelTree.refreshTree(rootHandle=lastNovel, overRide=True) + + return + def setFocus(self): """Forward the set focus call to the tree widget. """ @@ -90,16 +119,114 @@ class GuiNovelView(QWidget): """ return self.novelTree.hasFocus() + ## + # Public Slots + ## + + @pyqtSlot(str) + def updateRootItem(self, tHandle): + """Should be called whenever a root folders changes. + """ + self.novelBar.rebuildNovelRootMenu() + return + # END Class GuiNovelView class GuiNovelToolBar(QWidget): + rootFolderSelectionChanged = pyqtSignal(str) + def __init__(self, novelView): QTreeWidget.__init__(self, novelView) - self.mainConf = novelwriter.CONFIG - self.novelView = novelView + logger.debug("Initialising GuiNovelToolBar ...") + + self.mainConf = novelwriter.CONFIG + self.novelView = novelView + self.theProject = novelView.mainGui.theProject + self.mainTheme = novelView.mainGui.mainTheme + + iPx = self.mainTheme.baseIconSize + mPx = self.mainConf.pxInt(4) + + self.setContentsMargins(0, 0, 0, 0) + self.setAutoFillBackground(True) + + qPalette = self.palette() + qPalette.setBrush(QPalette.Window, qPalette.base()) + self.setPalette(qPalette) + + fadeCol = qPalette.text().color() + buttonStyle = ( + "QToolButton {{padding: {0}px; border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" + ).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue()) + + # Widget Label + self.viewLabel = QLabel("%s" % self.tr("Novel Outline")) + self.viewLabel.setContentsMargins(0, 0, 0, 0) + self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + # Novel Root Menu + self.mRoot = QMenu() + + self.tbRoot = QToolButton(self) + self.tbRoot.setToolTip(self.tr("Novel Root")) + self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])) + self.tbRoot.setIconSize(QSize(iPx, iPx)) + self.tbRoot.setStyleSheet(buttonStyle) + self.tbRoot.setMenu(self.mRoot) + self.tbRoot.setPopupMode(QToolButton.InstantPopup) + + # More Options Menu + self.mMore = QMenu() + + self.tbMore = QToolButton(self) + self.tbMore.setToolTip(self.tr("More Options")) + self.tbMore.setIcon(self.mainTheme.getIcon("menu")) + self.tbMore.setIconSize(QSize(iPx, iPx)) + self.tbMore.setStyleSheet(buttonStyle) + self.tbMore.setMenu(self.mMore) + self.tbMore.setPopupMode(QToolButton.InstantPopup) + + # Assemble + self.outerBox = QHBoxLayout() + self.outerBox.addWidget(self.viewLabel) + self.outerBox.addWidget(self.tbRoot) + self.outerBox.addWidget(self.tbMore) + self.outerBox.setContentsMargins(mPx, mPx, 0, mPx) + self.outerBox.setSpacing(0) + + self.setLayout(self.outerBox) + + logger.debug("GuiNovelToolBar initialisation complete") + + return + + ## + # Methods + ## + + def rebuildNovelRootMenu(self, selHandle=None): + """Build the novel root menu. + """ + self.mRoot.clear() + + agRoot = QActionGroup(self.mRoot) + for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(nwItemClass.NOVEL)): + aRoot = self.mRoot.addAction(nwItem.itemName) + aRoot.setData(tHandle) + aRoot.setCheckable(True) + aRoot.triggered.connect( + lambda n, tHandle=tHandle: self.rootFolderSelectionChanged.emit(tHandle) + ) + agRoot.addAction(aRoot) + + if n == 0: + aRoot.setChecked(True) + if selHandle == tHandle: + aRoot.setChecked(True) return @@ -128,41 +255,32 @@ class GuiNovelTree(QTreeWidget): self._lastBuild = 0 # Build GUI + # ========= + iPx = self.mainTheme.baseIconSize - self.setFrameStyle(QFrame.NoFrame) + cMg = self.mainConf.pxInt(6) + self.setIconSize(QSize(iPx, iPx)) + self.setFrameStyle(QFrame.NoFrame) + self.setHeaderHidden(True) self.setIndentation(iPx) self.setColumnCount(3) - self.setHeaderLabels([ - self.tr("Novel Outline"), - self.tr("Words"), - self.tr("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, self.tr("Section title")) - treeHeadItem.setToolTip(self.C_WORDS, self.tr("Word count")) - treeHeadItem.setToolTip(self.C_POV, self.tr("Point-of-view character")) - + # Lock the column sizes treeHeader = self.header() - treeHeader.setStretchLastSection(True) - treeHeader.setMinimumSectionSize(iPx + 6) + treeHeader.setStretchLastSection(False) + treeHeader.setMinimumSectionSize(iPx + cMg) + treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch) + treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents) + treeHeader.setSectionResizeMode(self.C_POV, QHeaderView.ResizeToContents) - # Get user's column width preferences for NAME and COUNT - treeColWidth = self.mainConf.getNovelColWidths() - if len(treeColWidth) <= 3: - for colN, colW in enumerate(treeColWidth): - self.setColumnWidth(colN, colW) - - # The last column should just auto-scale - self.resizeColumnToContents(self.C_POV) + # Connect signals + self.itemDoubleClicked.connect(self._treeDoubleClick) + self.itemSelectionChanged.connect(self._treeSelectionChange) # Set custom settings self.initSettings() @@ -199,12 +317,15 @@ class GuiNovelTree(QTreeWidget): self._lastBuild = 0 return - def refreshTree(self, overRide=False): + def refreshTree(self, rootHandle=None, overRide=False): """Called whenever the Novel tab is activated. """ logger.verbose("Requesting refresh of the novel tree") + if rootHandle is None: + rootHandle = self.theProject.tree.findRoot(nwItemClass.NOVEL) + treeChanged = self.mainGui.projView.changedSince(self._lastBuild) - indexChanged = self.theProject.index.indexChangedSince(self._lastBuild) + indexChanged = self.theProject.index.rootChangedSince(rootHandle, self._lastBuild) if not (treeChanged or indexChanged or overRide): logger.verbose("No changes have been made to the novel index") return @@ -214,7 +335,8 @@ class GuiNovelTree(QTreeWidget): if selItem: titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2] - self._populateTree() + self._populateTree(rootHandle) + self.theProject.setLastNovelViewed(rootHandle) if titleKey is not None and titleKey in self._treeMap: self._treeMap[titleKey].setSelected(True) @@ -268,39 +390,39 @@ class GuiNovelTree(QTreeWidget): if tHandle is None: return - self.mainGui.viewDocument(tHandle) + self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") return ## - # Slots + # Private Slots ## - def _treeDoubleClick(self, tItem, tCol): + @pyqtSlot() + def _treeSelectionChange(self): + """Extract the handle and line number of the currently selected + title, and send it to the tree meta panel. + """ + tHandle, _ = self.getSelectedHandle() + if tHandle is not None: + self.novelView.selectedItemChanged.emit(tHandle) + return + + @pyqtSlot("QTreeWidgetItem*", int) + def _treeDoubleClick(self, tItem, colNo): """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. """ tHandle, tLine = self.getSelectedHandle() - self.mainGui.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.mainGui.itemDetails.updateViewBox(tHandle) - + self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, tLine, "") return ## # Internal Functions ## - def _populateTree(self): + def _populateTree(self, rootHandle): """Build the tree based on the project index. """ self.clearTree() @@ -309,7 +431,9 @@ class GuiNovelTree(QTreeWidget): currChapter = None currScene = None - for tKey, tHandle, sTitle, novIdx in self.theProject.index.novelStructure(skipExcl=True): + logger.verbose("Building novel tree for root item '%s'", rootHandle) + novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True) + for tKey, tHandle, sTitle, novIdx in novStruct: tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx) self._treeMap[tKey] = tItem diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index b9bfda0d..4f320bfc 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -61,7 +61,7 @@ class GuiProjectView(QWidget): # Signals for user interaction with the project tree selectedItemChanged = pyqtSignal(str) - openDocumentRequest = pyqtSignal(str, Enum) + openDocumentRequest = pyqtSignal(str, Enum, int, str) def __init__(self, mainGui): QWidget.__init__(self, mainGui) @@ -192,10 +192,10 @@ class GuiProjectToolBar(QWidget): "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" ).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue()) - # Tree Label - self.projLabel = QLabel("%s" % self.tr("Project Content")) - self.projLabel.setContentsMargins(0, 0, 0, 0) - self.projLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + # Widget Label + self.viewLabel = QLabel("%s" % self.tr("Project Content")) + self.viewLabel.setContentsMargins(0, 0, 0, 0) + self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Move Buttons self.tbMoveU = QToolButton(self) @@ -285,7 +285,7 @@ class GuiProjectToolBar(QWidget): # Assemble self.outerBox = QHBoxLayout() - self.outerBox.addWidget(self.projLabel) + self.outerBox.addWidget(self.viewLabel) self.outerBox.addWidget(self.tbMoveU) self.outerBox.addWidget(self.tbMoveD) self.outerBox.addWidget(self.tbAdd) @@ -337,9 +337,8 @@ class GuiProjectTree(QTreeWidget): self._lastMove = {} self._timeChanged = 0 - ## - # Build GUI - ## + # Build GUI + # ========= # Context Menu self.setContextMenuPolicy(Qt.CustomContextMenu) @@ -348,6 +347,7 @@ class GuiProjectTree(QTreeWidget): # Tree Settings iPx = self.mainTheme.baseIconSize cMg = self.mainConf.pxInt(6) + self.setIconSize(QSize(iPx, iPx)) self.setFrameStyle(QFrame.NoFrame) self.setExpandsOnDoubleClick(False) @@ -972,7 +972,7 @@ class GuiProjectTree(QTreeWidget): return if tItem.itemType == nwItemType.FILE: - self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT) + self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "") else: trItem = self._getTreeItem(tHandle) if trItem is not None: @@ -1016,11 +1016,11 @@ class GuiProjectTree(QTreeWidget): if isFile: ctxMenu.addAction( self.tr("Open Document"), - lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT) + lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "") ) ctxMenu.addAction( self.tr("View Document"), - lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW) + lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") ) ctxMenu.addSeparator() @@ -1112,7 +1112,7 @@ class GuiProjectTree(QTreeWidget): return if tItem.itemType == nwItemType.FILE: - self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW) + self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") return diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 6fa52eb6..e06b9edd 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -205,6 +205,10 @@ class GuiMain(QMainWindow): self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo) self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox) self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem) + self.projView.rootFolderChanged.connect(self.novelView.updateRootItem) + + self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox) + self.novelView.openDocumentRequest.connect(self._openDocument) self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage) self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus) @@ -362,6 +366,7 @@ class GuiMain(QMainWindow): self.saveProject() self.docEditor.setDictionaries() self.outlineView.updateRootItem(None) + self.novelView.openProjectTasks() self.rebuildIndex(beQuiet=True) self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setProjectStatus(nwState.GOOD) @@ -509,6 +514,7 @@ class GuiMain(QMainWindow): self.docEditor.toggleSpellCheck(self.theProject.spellCheck) self.statusBar.setRefTime(self.theProject.projOpened) self.outlineView.updateRootItem(None) + self.novelView.openProjectTasks() self._updateStatusWordCount() # Restore previously open documents, if any @@ -825,7 +831,7 @@ class GuiMain(QMainWindow): """Rebuild the project tree. """ self.projView.populateTree() - self.novelView.refreshTree() + # self.novelView.refreshTree() return def requestNovelTreeRefresh(self): @@ -1468,15 +1474,15 @@ class GuiMain(QMainWindow): self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}") return - @pyqtSlot(str, Enum) - def _openDocument(self, tHandle, tMode): - """Handle an open document request. + @pyqtSlot(str, Enum, int, str) + def _openDocument(self, tHandle, tMode, tLine, tAnchor): + """Handle an open document request from one of the tree views. """ if tHandle is not None: if tMode == nwDocMode.EDIT: - self.openDocument(tHandle, changeFocus=False) + self.openDocument(tHandle, tLine=tLine, changeFocus=False) elif tMode == nwDocMode.VIEW: - self.viewDocument(tHandle=tHandle) + self.viewDocument(tHandle=tHandle, tAnchor=(tAnchor or None)) return @pyqtSlot(nwView) diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index e8ee5c59..af509400 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1345 - 229 - 68286 + 1367 + 231 + 68784 False @@ -17,6 +17,8 @@ True 636b6aa9b697b 636b6aa9b697b + 7031beac91f75 + None 1363 954 409 @@ -70,11 +72,11 @@ Chapter One - + Making a Scene - + Another Scene @@ -86,7 +88,7 @@ A Note on Structure - + Chapter Two From 88b2d61cec978044e77b310f50e95f921e502bfb Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jun 2022 17:54:37 +0200 Subject: [PATCH 03/10] Update tests --- tests/lipsum/nwProject.nwx | 2 ++ tests/minimal/nwProject.nwx | 2 ++ tests/reference/coreProject_NewCustomA_nwProject.nwx | 4 +++- tests/reference/coreProject_NewCustomB_nwProject.nwx | 4 +++- tests/reference/coreProject_NewFileFolder_nwProject.nwx | 4 +++- tests/reference/coreProject_NewMinimal_nwProject.nwx | 4 +++- tests/reference/coreProject_NewRoot_nwProject.nwx | 4 +++- tests/reference/guiEditor_Main_Final_nwProject.nwx | 4 +++- tests/reference/guiEditor_Main_Initial_nwProject.nwx | 4 +++- tests/reference/guiProjSettings_Dialog_nwProject.nwx | 4 +++- tests/test_gui/test_gui_guimain.py | 2 +- tests/test_gui/test_gui_noveltree.py | 4 ++-- 12 files changed, 31 insertions(+), 11 deletions(-) diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index 02e3df49..a8cf6393 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -16,6 +16,8 @@ True 7a992350f3eb6 None + None + None 3847 3109 738 diff --git a/tests/minimal/nwProject.nwx b/tests/minimal/nwProject.nwx index af7595a4..430ecf17 100644 --- a/tests/minimal/nwProject.nwx +++ b/tests/minimal/nwProject.nwx @@ -17,6 +17,8 @@ True None None + None + None 10 10 0 diff --git a/tests/reference/coreProject_NewCustomA_nwProject.nwx b/tests/reference/coreProject_NewCustomA_nwProject.nwx index be4845bf..f046fceb 100644 --- a/tests/reference/coreProject_NewCustomA_nwProject.nwx +++ b/tests/reference/coreProject_NewCustomA_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Custom Test Novel @@ -17,6 +17,8 @@ True None None + None + None 0 0 0 diff --git a/tests/reference/coreProject_NewCustomB_nwProject.nwx b/tests/reference/coreProject_NewCustomB_nwProject.nwx index 12f911cd..5d02172c 100644 --- a/tests/reference/coreProject_NewCustomB_nwProject.nwx +++ b/tests/reference/coreProject_NewCustomB_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Custom Test Novel @@ -17,6 +17,8 @@ True None None + None + None 0 0 0 diff --git a/tests/reference/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx index 4235e9bb..20aeb027 100644 --- a/tests/reference/coreProject_NewFileFolder_nwProject.nwx +++ b/tests/reference/coreProject_NewFileFolder_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project New Novel @@ -16,6 +16,8 @@ True None None + None + None 2 1 1 diff --git a/tests/reference/coreProject_NewMinimal_nwProject.nwx b/tests/reference/coreProject_NewMinimal_nwProject.nwx index 422bb6b2..ba08600f 100644 --- a/tests/reference/coreProject_NewMinimal_nwProject.nwx +++ b/tests/reference/coreProject_NewMinimal_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -15,6 +15,8 @@ True None None + None + None 0 0 0 diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx index cd25c1cf..0a606137 100644 --- a/tests/reference/coreProject_NewRoot_nwProject.nwx +++ b/tests/reference/coreProject_NewRoot_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project New Novel @@ -16,6 +16,8 @@ True None None + None + None 0 0 0 diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index a47d454d..ecc96604 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project New Novel @@ -16,6 +16,8 @@ True 000000000000f None + 0000000000008 + None 129 102 27 diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx index 03313570..1a79d2c2 100644 --- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project New Novel @@ -16,6 +16,8 @@ True None None + None + None 9 9 0 diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx index f12c1dc2..883cb26d 100644 --- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx +++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx @@ -1,5 +1,5 @@ - + Project Name Project Title @@ -17,6 +17,8 @@ True None None + None + None 9 9 0 diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 9a56af3d..b197885e 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -132,7 +132,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): # Novel Tree has focus nwGUI.projStack.setCurrentIndex(1) - nwGUI.novelView.refreshTree(True) + nwGUI.novelView.refreshTree(rootHandle=None, overRide=True) with monkeypatch.context() as mp: mp.setattr(GuiNovelView, "treeFocus", lambda *a: True) assert nwGUI.docEditor.docHandle() is None diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index 644839d3..b1e223cf 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -62,7 +62,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView) nwGUI.rebuildIndex() - novelTree._populateTree() + novelTree._populateTree(rootHandle=None) assert novelTree.topLevelItemCount() == 1 # Rebuild should preserve selection @@ -133,7 +133,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): "#### Section\n\n" )) nwGUI.rebuildIndex() - novelTree._populateTree() + novelTree._populateTree(None) assert novelTree.topLevelItem(0).text(novelTree.C_TITLE) == "Section wo/Scene" assert novelTree.topLevelItem(1).text(novelTree.C_TITLE) == "Scene wo/Chapter" assert novelTree.topLevelItem(2).text(novelTree.C_TITLE) == "Chapter wo/Title" From 80404417574c63dbbf2e74f12163be51394028dc Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:59:18 +0200 Subject: [PATCH 04/10] Allow changing content of last column of the novel tree --- novelwriter/core/options.py | 3 +- novelwriter/gui/noveltree.py | 117 +++++++++++++++++++++++++++++++++-- novelwriter/guimain.py | 1 + 3 files changed, 114 insertions(+), 7 deletions(-) diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py index 144ba4ac..b3724047 100644 --- a/novelwriter/core/options.py +++ b/novelwriter/core/options.py @@ -56,7 +56,8 @@ VALID_MAP = { "winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2", "widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble" }, - "GuiWordList": {"winWidth", "winHeight"} + "GuiWordList": {"winWidth", "winHeight"}, + "GuiNovelView": {"lastCol"}, } diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 8cec9fdb..a23e1e9e 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -39,11 +39,20 @@ from PyQt5.QtWidgets import ( from novelwriter.enum import nwDocMode, nwItemClass from novelwriter.common import checkInt -from novelwriter.constants import nwKeyWords, nwLabels +from novelwriter.constants import nwKeyWords, nwLabels, trConst logger = logging.getLogger(__name__) +class NovelColumnType(Enum): + + HIDDEN = 0 + POV = 1 + FOCUS = 2 + +# END Enum NovelColumnType + + class GuiNovelView(QWidget): # Signals for user interaction with the novel tree @@ -94,7 +103,7 @@ class GuiNovelView(QWidget): return def openProjectTasks(self): - """Run tasks related to opening a project. + """Run tasks when opening a project. """ lastNovel = self.theProject.lastNovel if lastNovel is None: @@ -104,10 +113,17 @@ class GuiNovelView(QWidget): self.clearProject() self.novelBar.rebuildNovelRootMenu(selHandle=lastNovel) + self.novelTree.loadOptions() self.novelTree.refreshTree(rootHandle=lastNovel, overRide=True) return + def closeProjectTasks(self): + """Run tasks when closing a project. + """ + self.novelTree.saveOptions() + return + def setFocus(self): """Forward the set focus call to the tree widget. """ @@ -168,6 +184,14 @@ class GuiNovelToolBar(QWidget): self.viewLabel.setContentsMargins(0, 0, 0, 0) self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + # Refresh Button + self.tbRefresh = QToolButton(self) + self.tbRefresh.setToolTip(self.tr("Refresh")) + self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh")) + self.tbRefresh.setIconSize(QSize(iPx, iPx)) + self.tbRefresh.setStyleSheet(buttonStyle) + self.tbRefresh.clicked.connect(self._refreshNovelTree) + # Novel Root Menu self.mRoot = QMenu() @@ -182,6 +206,17 @@ class GuiNovelToolBar(QWidget): # More Options Menu self.mMore = QMenu() + self.mCol3 = self.mMore.addMenu(self.tr("Third Column")) + self.mCol3.addAction(self.tr("Hide Column")).triggered.connect( + lambda: self.novelView.novelTree.setLastColType(NovelColumnType.HIDDEN) + ) + self.mCol3.addAction(self.tr("Point of View Character")).triggered.connect( + lambda: self.novelView.novelTree.setLastColType(NovelColumnType.POV) + ) + self.mCol3.addAction(self.tr("Focus Character")).triggered.connect( + lambda: self.novelView.novelTree.setLastColType(NovelColumnType.FOCUS) + ) + self.tbMore = QToolButton(self) self.tbMore.setToolTip(self.tr("More Options")) self.tbMore.setIcon(self.mainTheme.getIcon("menu")) @@ -193,6 +228,7 @@ class GuiNovelToolBar(QWidget): # Assemble self.outerBox = QHBoxLayout() self.outerBox.addWidget(self.viewLabel) + self.outerBox.addWidget(self.tbRefresh) self.outerBox.addWidget(self.tbRoot) self.outerBox.addWidget(self.tbMore) self.outerBox.setContentsMargins(mPx, mPx, 0, mPx) @@ -230,6 +266,18 @@ class GuiNovelToolBar(QWidget): return + ## + # Private Slots + ## + + @pyqtSlot() + def _refreshNovelTree(self): + """Rebuild the current tree. + """ + rootHandle = self.theProject.lastNovel + self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) + return + # END Class GuiNovelToolBar @@ -237,7 +285,7 @@ class GuiNovelTree(QTreeWidget): C_TITLE = 0 C_WORDS = 1 - C_POV = 2 + C_LAST = 2 def __init__(self, novelView): QTreeWidget.__init__(self, novelView) @@ -253,6 +301,11 @@ class GuiNovelTree(QTreeWidget): # Internal Variables self._treeMap = {} self._lastBuild = 0 + self._lastCol = NovelColumnType.POV + + # Cached i18n Strings + self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) + self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]) # Build GUI # ========= @@ -276,7 +329,7 @@ class GuiNovelTree(QTreeWidget): treeHeader.setMinimumSectionSize(iPx + cMg) treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch) treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents) - treeHeader.setSectionResizeMode(self.C_POV, QHeaderView.ResizeToContents) + treeHeader.setSectionResizeMode(self.C_LAST, QHeaderView.ResizeToContents) # Connect signals self.itemDoubleClicked.connect(self._treeDoubleClick) @@ -317,6 +370,32 @@ class GuiNovelTree(QTreeWidget): self._lastBuild = 0 return + def loadOptions(self): + """Load user options. + """ + try: + lastCol = NovelColumnType[self.theProject.options.getString( + "GuiNovelView", "lastCol", NovelColumnType.POV.name + )] + except Exception: + logger.error("Failed to load last column type from options") + return False + + self._lastCol = lastCol + self.setColumnHidden(self.C_LAST, lastCol == NovelColumnType.HIDDEN) + + return True + + def saveOptions(self): + """Save user options. + """ + try: + self.theProject.options.setValue("GuiNovelView", "lastCol", self._lastCol.name) + except Exception: + logger.error("Failed to save last column type to options") + return False + return True + def refreshTree(self, rootHandle=None, overRide=False): """Called whenever the Novel tab is activated. """ @@ -365,6 +444,16 @@ class GuiNovelTree(QTreeWidget): return tHandle, tLine + def setLastColType(self, colType): + """Change the content type of the last column and rebuild. + """ + if self._lastCol != colType: + logger.debug("Changing last column to %s", colType.name) + self._lastCol = colType + self.setColumnHidden(self.C_LAST, colType == NovelColumnType.HIDDEN) + self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) + return + ## # Events ## @@ -431,6 +520,8 @@ class GuiNovelTree(QTreeWidget): currChapter = None currScene = None + tStart = time() + logger.verbose("Building novel tree for root item '%s'", rootHandle) novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True) for tKey, tHandle, sTitle, novIdx in novStruct: @@ -477,6 +568,8 @@ class GuiNovelTree(QTreeWidget): tItem.setExpanded(True) + logger.verbose("Novel Tree built in %.3f ms", (time() - tStart)*1000) + self._lastBuild = time() return @@ -496,8 +589,20 @@ class GuiNovelTree(QTreeWidget): newItem.setText(self.C_WORDS, f"{wC:n}") newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) - theRefs = self.theProject.index.getReferences(tHandle, sTitle) - newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY])) + if self._lastCol == NovelColumnType.HIDDEN: + newItem.setText(self.C_LAST, "") + else: + theRefs = self.theProject.index.getReferences(tHandle, sTitle) + if self._lastCol == NovelColumnType.POV: + newText = ", ".join(theRefs[nwKeyWords.POV_KEY]) + newItem.setText(self.C_LAST, newText) + if newText: + newItem.setToolTip(self.C_LAST, f"{self._povLabel}: {newText}") + elif self._lastCol == NovelColumnType.FOCUS: + newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY]) + newItem.setText(self.C_LAST, newText) + if newText: + newItem.setToolTip(self.C_LAST, f"{self._focLabel}: {newText}") return newItem diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index e06b9edd..d3e36e90 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -423,6 +423,7 @@ class GuiMain(QMainWindow): self.closeDocument() self.docViewer.clearNavHistory() self.outlineView.closeOutline() + self.novelView.closeProjectTasks() self.theProject.closeProject(self.idleTime) self.idleRefTime = time() From 49ace054bdb1982c63dfa32a430eff525f771b9f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jun 2022 20:18:24 +0200 Subject: [PATCH 05/10] Make options class handle enums, and add plot column to novel tree --- novelwriter/core/options.py | 19 ++++++++++++++++++- novelwriter/gui/noveltree.py | 33 ++++++++++++++++----------------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py index b3724047..d64970ae 100644 --- a/novelwriter/core/options.py +++ b/novelwriter/core/options.py @@ -28,6 +28,8 @@ import os import json import logging +from enum import Enum + from novelwriter.error import logException from novelwriter.common import checkBool, checkFloat, checkInt, checkString from novelwriter.constants import nwFiles @@ -138,7 +140,10 @@ class OptionState(): if group not in self._theState: self._theState[group] = {} - self._theState[group][name] = value + if isinstance(value, Enum): + self._theState[group][name] = value.name + else: + self._theState[group][name] = value return True @@ -187,4 +192,16 @@ class OptionState(): return checkBool(self._theState[group].get(name, default), default) return default + def getEnum(self, group, name, lookup, default): + """Return the value mapped to an enum. Otherwise return the + default value + """ + if issubclass(lookup, Enum): + if group in self._theState: + if name in self._theState[group]: + value = self._theState[group][name] + if value in lookup.__members__: + return lookup[value] + return default + # END Class OptionState diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index a23e1e9e..8bfd207a 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -49,6 +49,7 @@ class NovelColumnType(Enum): HIDDEN = 0 POV = 1 FOCUS = 2 + PLOT = 3 # END Enum NovelColumnType @@ -216,6 +217,9 @@ class GuiNovelToolBar(QWidget): self.mCol3.addAction(self.tr("Focus Character")).triggered.connect( lambda: self.novelView.novelTree.setLastColType(NovelColumnType.FOCUS) ) + self.mCol3.addAction(self.tr("Novel Plot")).triggered.connect( + lambda: self.novelView.novelTree.setLastColType(NovelColumnType.PLOT) + ) self.tbMore = QToolButton(self) self.tbMore.setToolTip(self.tr("More Options")) @@ -306,6 +310,7 @@ class GuiNovelTree(QTreeWidget): # Cached i18n Strings self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]) + self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]) # Build GUI # ========= @@ -373,28 +378,17 @@ class GuiNovelTree(QTreeWidget): def loadOptions(self): """Load user options. """ - try: - lastCol = NovelColumnType[self.theProject.options.getString( - "GuiNovelView", "lastCol", NovelColumnType.POV.name - )] - except Exception: - logger.error("Failed to load last column type from options") - return False - - self._lastCol = lastCol - self.setColumnHidden(self.C_LAST, lastCol == NovelColumnType.HIDDEN) - + self._lastCol = self.theProject.options.getEnum( + "GuiNovelView", "lastCol", NovelColumnType, NovelColumnType.POV + ) + self.setColumnHidden(self.C_LAST, self._lastCol == NovelColumnType.HIDDEN) return True def saveOptions(self): """Save user options. """ - try: - self.theProject.options.setValue("GuiNovelView", "lastCol", self._lastCol.name) - except Exception: - logger.error("Failed to save last column type to options") - return False - return True + self.theProject.options.setValue("GuiNovelView", "lastCol", self._lastCol) + return def refreshTree(self, rootHandle=None, overRide=False): """Called whenever the Novel tab is activated. @@ -603,6 +597,11 @@ class GuiNovelTree(QTreeWidget): newItem.setText(self.C_LAST, newText) if newText: newItem.setToolTip(self.C_LAST, f"{self._focLabel}: {newText}") + elif self._lastCol == NovelColumnType.PLOT: + newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY]) + newItem.setText(self.C_LAST, newText) + if newText: + newItem.setToolTip(self.C_LAST, f"{self._pltLabel}: {newText}") return newItem From ea61db9591316db21774840411e7fd4a021ec608 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 14 Jun 2022 00:14:51 +0200 Subject: [PATCH 06/10] Simplify the novel outline tree --- novelwriter/constants.py | 9 ++ novelwriter/core/index.py | 20 ++--- novelwriter/core/project.py | 2 +- novelwriter/gui/noveltree.py | 164 ++++++++++++++++------------------- novelwriter/guimain.py | 20 ++--- 5 files changed, 101 insertions(+), 114 deletions(-) diff --git a/novelwriter/constants.py b/novelwriter/constants.py index dc62dbd3..3d1b5c13 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -57,6 +57,15 @@ class nwRegEx: # END Class nwRegEx +class nwHeaders: + + H_VALID = ("H0", "H1", "H2", "H3", "H4") + H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4} + TT_NONE = "T000000" + +# END Class nwHeaders + + class nwFiles: PROJ_FILE = "nwProject.nwx" diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index ee9ea089..0b3afdc5 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -34,7 +34,7 @@ from time import time from novelwriter.enum import nwItemType, nwItemLayout from novelwriter.error import logException -from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode +from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders from novelwriter.core.document import NWDoc from novelwriter.common import ( checkInt, isHandle, isItemClass, isTitleTag, jsonEncode @@ -42,10 +42,6 @@ from novelwriter.common import ( logger = logging.getLogger(__name__) -H_VALID = ("H0", "H1", "H2", "H3", "H4") -H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4} -TT_NONE = "T000000" - class NWIndex: """This class holds the entire index for a given project. The index @@ -477,7 +473,7 @@ class NWIndex: """ hCount = [0, 0, 0, 0, 0] for _, _, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl): - iLevel = H_LEVEL.get(hItem.level, 0) + iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) hCount[iLevel] += 1 return hCount @@ -510,7 +506,7 @@ class NWIndex: pKey = None for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl): tKey = f"{tHandle}:{sTitle}" - iLevel = H_LEVEL.get(hItem.level, 0) + iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if iLevel > maxDepth: if pKey in tData: tData[pKey]["words"] += hItem.wordCount @@ -660,7 +656,7 @@ class TagsIndex: """ if tagKey in self._tags: return self._tags.get(tagKey).get("heading") - return TT_NONE + return nwHeaders.TT_NONE def tagClass(self, tagKey): """Get the class of a given tag. @@ -906,7 +902,7 @@ class IndexItem: self._index = 0 # Add a placeholder heading - self._headings[TT_NONE] = IndexHeading(TT_NONE) + self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE) return @@ -940,8 +936,8 @@ class IndexItem: """Add a heading to the item. Also remove the placeholder entry if it exists. """ - if TT_NONE in self._headings: - self._headings.pop(TT_NONE) + if nwHeaders.TT_NONE in self._headings: + self._headings.pop(nwHeaders.TT_NONE) self._headings[tHeading.key] = tHeading return @@ -1110,7 +1106,7 @@ class IndexHeading: def setLevel(self, level): """Set the level of the header if it's a valid value. """ - if level in H_VALID: + if level in nwHeaders.H_VALID: self._level = level return diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 43f3297e..fb81f033 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -653,7 +653,7 @@ class NWProject(): self._legacyDataFolder(projItem) except Exception: self.mainGui.makeAlert(self.tr( - "There was an error while converting project version 1.0. " + "There was an error updating the project. " "Some data may not have been preserved." ), nwAlert.ERROR) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 8bfd207a..33981243 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -4,7 +4,9 @@ novelWriter – GUI Novel Tree GUI classe for the main window novel tree File History: -Created: 2020-12-20 [1.1a0] +Created: 2020-12-20 [1.1a0] GuiNovelTree +Created: 2022-06-12 [1.7b1] GuiNovelView +Created: 2022-06-12 [1.7b1] GuiNovelToolBar This file is a part of novelWriter Copyright 2018–2020, Veronica Berglyd Olsen @@ -30,7 +32,7 @@ from time import time from enum import Enum from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal -from PyQt5.QtGui import QPalette +from PyQt5.QtGui import QPalette, QPixmap, QColor from PyQt5.QtWidgets import ( QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel, QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, @@ -39,19 +41,19 @@ from PyQt5.QtWidgets import ( from novelwriter.enum import nwDocMode, nwItemClass from novelwriter.common import checkInt -from novelwriter.constants import nwKeyWords, nwLabels, trConst +from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst logger = logging.getLogger(__name__) -class NovelColumnType(Enum): +class NovelTreeColumn(Enum): HIDDEN = 0 POV = 1 FOCUS = 2 PLOT = 3 -# END Enum NovelColumnType +# END Enum NovelTreeColumn class GuiNovelView(QWidget): @@ -209,16 +211,16 @@ class GuiNovelToolBar(QWidget): self.mCol3 = self.mMore.addMenu(self.tr("Third Column")) self.mCol3.addAction(self.tr("Hide Column")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelColumnType.HIDDEN) + lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.HIDDEN) ) self.mCol3.addAction(self.tr("Point of View Character")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelColumnType.POV) + lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.POV) ) self.mCol3.addAction(self.tr("Focus Character")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelColumnType.FOCUS) + lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.FOCUS) ) self.mCol3.addAction(self.tr("Novel Plot")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelColumnType.PLOT) + lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.PLOT) ) self.tbMore = QToolButton(self) @@ -305,9 +307,9 @@ class GuiNovelTree(QTreeWidget): # Internal Variables self._treeMap = {} self._lastBuild = 0 - self._lastCol = NovelColumnType.POV + self._lastCol = NovelTreeColumn.POV - # Cached i18n Strings + # Cached Strings self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]) self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]) @@ -316,12 +318,15 @@ class GuiNovelTree(QTreeWidget): # ========= iPx = self.mainTheme.baseIconSize + nPx = self.mainTheme.textNWidth cMg = self.mainConf.pxInt(6) + mPx = self.mainConf.pxInt(4) + nMg = self.mainConf.pxInt(6) - self.setIconSize(QSize(iPx, iPx)) + # self.setIconSize(QSize(iPx, iPx)) self.setFrameStyle(QFrame.NoFrame) self.setHeaderHidden(True) - self.setIndentation(iPx) + self.setIndentation(mPx) self.setColumnCount(3) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.SingleSelection) @@ -336,6 +341,25 @@ class GuiNovelTree(QTreeWidget): treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents) treeHeader.setSectionResizeMode(self.C_LAST, QHeaderView.ResizeToContents) + # Pre-Generate Tree Formatting + fH1 = self.font() + fH1.setBold(True) + fH1.setUnderline(True) + + fH2 = self.font() + fH2.setBold(True) + + self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()] + self._hIndent = ["", "", "", "\u2022\u00a0", "\u00bb\u00a0"] + self._pIndent = [QPixmap(), QPixmap()] + + hPix = QPixmap(QSize(iPx, iPx)) + hPix.fill(QColor(0, 0, 0, 0)) + for m in range(1, 4): + self._pIndent.append(hPix.scaled( + max(nPx*m - nMg, nMg), 2, Qt.IgnoreAspectRatio, Qt.FastTransformation + )) + # Connect signals self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemSelectionChanged.connect(self._treeSelectionChange) @@ -379,9 +403,9 @@ class GuiNovelTree(QTreeWidget): """Load user options. """ self._lastCol = self.theProject.options.getEnum( - "GuiNovelView", "lastCol", NovelColumnType, NovelColumnType.POV + "GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.POV ) - self.setColumnHidden(self.C_LAST, self._lastCol == NovelColumnType.HIDDEN) + self.setColumnHidden(self.C_LAST, self._lastCol == NovelTreeColumn.HIDDEN) return True def saveOptions(self): @@ -444,7 +468,7 @@ class GuiNovelTree(QTreeWidget): if self._lastCol != colType: logger.debug("Changing last column to %s", colType.name) self._lastCol = colType - self.setColumnHidden(self.C_LAST, colType == NovelColumnType.HIDDEN) + self.setColumnHidden(self.C_LAST, colType == NovelTreeColumn.HIDDEN) self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) return @@ -509,100 +533,58 @@ class GuiNovelTree(QTreeWidget): """Build the tree based on the project index. """ self.clearTree() - - currTitle = None - currChapter = None - currScene = None - tStart = time() - logger.verbose("Building novel tree for root item '%s'", rootHandle) + novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True) for tKey, tHandle, sTitle, novIdx in novStruct: - tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx) - self._treeMap[tKey] = tItem + iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0) + if iLevel == 0: + continue - tLevel = novIdx.level - if tLevel == "H1": - self.addTopLevelItem(tItem) - currTitle = tItem - currChapter = None - currScene = None + newItem = QTreeWidgetItem() + theData = (tHandle, sTitle[1:].lstrip("0"), tKey) - elif tLevel == "H2": - if currTitle is None: - self.addTopLevelItem(tItem) - else: - currTitle.addChild(tItem) - currChapter = tItem - currScene = None + newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel]) + newItem.setText(self.C_TITLE, self._hIndent[iLevel] + novIdx.title) + newItem.setData(self.C_TITLE, Qt.UserRole, theData) + newItem.setFont(self.C_TITLE, self._hFonts[iLevel]) + newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}") + newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) - elif tLevel == "H3": - if currChapter is None: - if currTitle is None: - self.addTopLevelItem(tItem) - else: - currTitle.addChild(tItem) - else: - currChapter.addChild(tItem) - currScene = tItem + lastText, toolTip = self._getLastColumnText(tHandle, sTitle) + newItem.setText(self.C_LAST, lastText) + if lastText: + newItem.setToolTip(self.C_LAST, toolTip) - 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._treeMap[tKey] = newItem + self.addTopLevelItem(newItem) logger.verbose("Novel Tree built in %.3f ms", (time() - tStart)*1000) - self._lastBuild = time() return - def _createTreeItem(self, tHandle, sTitle, titleKey, novIdx): - """Populate a tree item with all the column values. + def _getLastColumnText(self, tHandle, sTitle): + """Generate the text for the last column based on user settings. """ - newItem = QTreeWidgetItem() - hIcon = "doc_%s" % novIdx.level.lower() - theData = (tHandle, sTitle[1:].lstrip("0"), titleKey) + if self._lastCol == NovelTreeColumn.HIDDEN: + return "", "" - wC = int(novIdx.wordCount) + theRefs = self.theProject.index.getReferences(tHandle, sTitle) + if self._lastCol == NovelTreeColumn.POV: + newText = ", ".join(theRefs[nwKeyWords.POV_KEY]) + return newText, f"{self._povLabel}: {newText}" - newItem.setText(self.C_TITLE, novIdx.title) - newItem.setData(self.C_TITLE, Qt.UserRole, theData) - newItem.setIcon(self.C_TITLE, self.mainTheme.getIcon(hIcon)) - newItem.setText(self.C_WORDS, f"{wC:n}") - newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) + elif self._lastCol == NovelTreeColumn.FOCUS: + newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY]) + return newText, f"{self._focLabel}: {newText}" - if self._lastCol == NovelColumnType.HIDDEN: - newItem.setText(self.C_LAST, "") - else: - theRefs = self.theProject.index.getReferences(tHandle, sTitle) - if self._lastCol == NovelColumnType.POV: - newText = ", ".join(theRefs[nwKeyWords.POV_KEY]) - newItem.setText(self.C_LAST, newText) - if newText: - newItem.setToolTip(self.C_LAST, f"{self._povLabel}: {newText}") - elif self._lastCol == NovelColumnType.FOCUS: - newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY]) - newItem.setText(self.C_LAST, newText) - if newText: - newItem.setToolTip(self.C_LAST, f"{self._focLabel}: {newText}") - elif self._lastCol == NovelColumnType.PLOT: - newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY]) - newItem.setText(self.C_LAST, newText) - if newText: - newItem.setToolTip(self.C_LAST, f"{self._pltLabel}: {newText}") + elif self._lastCol == NovelTreeColumn.PLOT: + newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY]) + return newText, f"{self._pltLabel}: {newText}" - return newItem + return "", "" # END Class GuiNovelTree diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index d3e36e90..71d99e33 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1582,30 +1582,30 @@ class GuiMain(QMainWindow): return @pyqtSlot(int) - def _mainStackChanged(self, tabIndex): + def _mainStackChanged(self, stIndex): """Activated when the main window tab is changed. """ - if tabIndex == self.idxEditorView: - logger.verbose("Editor tab activated") - elif tabIndex == self.idxOutlineView: - logger.verbose("Project outline tab activated") + if stIndex == self.idxEditorView: + logger.verbose("Editor View activated") + elif stIndex == self.idxOutlineView: + logger.verbose("Outline View activated") if self.hasProject: self.outlineView.refreshView() return @pyqtSlot(int) - def _projStackChanged(self, tabIndex): + def _projStackChanged(self, stIndex): """Activated when the project view tab is changed. """ sHandle = None - if tabIndex == self.idxProjView: - logger.verbose("Project tree tab activated") + if stIndex == self.idxProjView: + logger.verbose("Project Tree View activated") sHandle = self.projView.getSelectedHandle() - elif tabIndex == self.idxNovelView: - logger.verbose("Novel tree tab activated") + elif stIndex == self.idxNovelView: + logger.verbose("Novel Tree View activated") if self.hasProject: self.novelView.refreshTree() sHandle, _ = self.novelView.getSelectedHandle() From 538b722bdf7750921df9668d2e2ab40ba2c98b09 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 14 Jun 2022 19:33:12 +0200 Subject: [PATCH 07/10] Fix tests and a few other bits --- novelwriter/gui/doceditor.py | 2 + novelwriter/gui/noveltree.py | 1 - sample/content/a520879ca0b45.nwd | 2 +- sample/content/edca4be2fcaf8.nwd | 2 +- tests/test_core/test_core_project.py | 2 +- tests/test_gui/test_gui_guimain.py | 12 ++--- tests/test_gui/test_gui_noveltree.py | 66 +++++----------------------- 7 files changed, 22 insertions(+), 65 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 25552037..0d32689e 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -511,11 +511,13 @@ class GuiDocEditor(QTextEdit): self.theProject.index.scanText(tHandle, docText) newHeader = self.theProject.index.getHandleHeaderLevel(tHandle) + # ToDo: This should be a signal if self._updateHeaders(checkLevel=True): self.mainGui.requestNovelTreeRefresh() else: self.mainGui.novelView.updateWordCounts(tHandle) + # ToDo: This should be a signal if oldHeader != newHeader: self.mainGui.projView.setTreeItemValues(tHandle) self.mainGui.itemDetails.updateViewBox(tHandle) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 33981243..336058a5 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -254,7 +254,6 @@ class GuiNovelToolBar(QWidget): """Build the novel root menu. """ self.mRoot.clear() - agRoot = QActionGroup(self.mRoot) for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(nwItemClass.NOVEL)): aRoot = self.mRoot.addAction(nwItem.itemName) diff --git a/sample/content/a520879ca0b45.nwd b/sample/content/a520879ca0b45.nwd index 12d7a62a..75ba39c8 100644 --- a/sample/content/a520879ca0b45.nwd +++ b/sample/content/a520879ca0b45.nwd @@ -1,7 +1,7 @@ %%~name: Chapter One %%~path: e5e47ebf63b1c/a520879ca0b45 %%~kind: NOVEL/DOCUMENT -### Chapter One +## Chapter One @pov: Jane diff --git a/sample/content/edca4be2fcaf8.nwd b/sample/content/edca4be2fcaf8.nwd index 3028b3fd..8b3efc78 100644 --- a/sample/content/edca4be2fcaf8.nwd +++ b/sample/content/edca4be2fcaf8.nwd @@ -3,4 +3,4 @@ %%~kind: NOVEL/DOCUMENT # Part One ->> In the beginning … << \ No newline at end of file +>> In the beginning … << diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index bdc27771..a6409b98 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -470,7 +470,7 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI): writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff") mockGUI.clear() assert theProject.openProject(nwMinimal) is True - assert "version 1.0" in mockGUI.lastAlert + assert "There was an error updating the project." in mockGUI.lastAlert assert theProject.closeProject() diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index b197885e..c308451b 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -29,7 +29,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMessageBox, QInputDialog from novelwriter.gui import GuiDocEditor, GuiNovelView, GuiOutlineView -from novelwriter.enum import nwItemType, nwWidget +from novelwriter.enum import nwItemType, nwView, nwWidget from novelwriter.tools import GuiProjectWizard from novelwriter.gui.projtree import GuiProjectTree from novelwriter.dialogs import GuiEditLabel @@ -120,6 +120,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): assert nwGUI.openSelectedItem() is False # Project Tree has focus + nwGUI._changeView(nwView.PROJECT) nwGUI.switchFocus(nwWidget.TREE) nwGUI.projStack.setCurrentIndex(0) with monkeypatch.context() as mp: @@ -131,20 +132,19 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): assert nwGUI.closeDocument() is True # Novel Tree has focus - nwGUI.projStack.setCurrentIndex(1) + nwGUI._changeView(nwView.NOVEL) nwGUI.novelView.refreshTree(rootHandle=None, overRide=True) with monkeypatch.context() as mp: mp.setattr(GuiNovelView, "treeFocus", lambda *a: True) assert nwGUI.docEditor.docHandle() is None - actItem = nwGUI.novelView.novelTree.topLevelItem(0) - chpItem = actItem.child(0) - selItem = chpItem.child(0) + selItem = nwGUI.novelView.novelTree.topLevelItem(2) nwGUI.novelView.novelTree.setCurrentItem(selItem) nwGUI._keyPressReturn() assert nwGUI.docEditor.docHandle() == sHandle assert nwGUI.closeDocument() is True # Project Outline has focus + nwGUI._changeView(nwView.OUTLINE) nwGUI.switchFocus(nwWidget.OUTLINE) with monkeypatch.context() as mp: mp.setattr(GuiOutlineView, "treeFocus", lambda *a: True) @@ -157,7 +157,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): assert nwGUI.docEditor.docHandle() == sHandle assert nwGUI.closeDocument() is True - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiMain_ProjectTreeItems diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index b1e223cf..ea8b49bc 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -20,9 +20,6 @@ along with this program. If not, see . """ import pytest -import os - -from tools import writeFile from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMessageBox @@ -40,9 +37,8 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): novelView = nwGUI.novelView novelTree = novelView.novelTree - ## - # Show/Hide Scrollbars - ## + # Show/Hide Scrollbars + # ==================== nwGUI.mainConf.hideVScroll = True nwGUI.mainConf.hideHScroll = True @@ -56,14 +52,13 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): assert novelTree.verticalScrollBar().isEnabled() assert novelTree.horizontalScrollBar().isEnabled() - ## - # Populate Tree - ## + # Populate Tree + # ============= nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView) nwGUI.rebuildIndex() novelTree._populateTree(rootHandle=None) - assert novelTree.topLevelItemCount() == 1 + assert novelTree.topLevelItemCount() == 3 # Rebuild should preserve selection topItem = novelTree.topLevelItem(0) @@ -75,13 +70,12 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): novelView.refreshTree() assert novelTree.topLevelItem(0).isSelected() - ## - # Open Items - ## + # Open Items + # ========== # Clear selection novelTree.clearSelection() - scItem = novelTree.topLevelItem(0).child(0).child(0) + scItem = novelTree.topLevelItem(2) scItem.setSelected(True) assert scItem.isSelected() @@ -114,48 +108,10 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) assert nwGUI.docViewer.docHandle() == "8c659a11cd429" - ## - # Populate Tree - ## + # Close + # ===== - # Add weird titles to first file to check hnadling of non-standard - # order of title levels. - writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), ( - "#### Section wo/Scene\n\n" - "### Scene wo/Chapter\n\n" - "## Chapter wo/Title\n\n" - "# Title\n\n" - "#### Section w/Title, wo/Scene\n\n" - "### Scene w/Title, wo/Chapter\n\n" - "## Chapter\n\n" - "#### Section w/Chapter, wo/Scene\n\n" - "### Scene\n\n" - "#### Section\n\n" - )) - nwGUI.rebuildIndex() - novelTree._populateTree(None) - assert novelTree.topLevelItem(0).text(novelTree.C_TITLE) == "Section wo/Scene" - assert novelTree.topLevelItem(1).text(novelTree.C_TITLE) == "Scene wo/Chapter" - assert novelTree.topLevelItem(2).text(novelTree.C_TITLE) == "Chapter wo/Title" - assert novelTree.topLevelItem(3).text(novelTree.C_TITLE) == "Title" - - tTitle = novelTree.topLevelItem(3) - assert tTitle.child(0).text(novelTree.C_TITLE) == "Section w/Title, wo/Scene" - assert tTitle.child(1).text(novelTree.C_TITLE) == "Scene w/Title, wo/Chapter" - assert tTitle.child(2).text(novelTree.C_TITLE) == "Chapter" - - tChap = tTitle.child(2) - assert tChap.child(0).text(novelTree.C_TITLE) == "Section w/Chapter, wo/Scene" - assert tChap.child(1).text(novelTree.C_TITLE) == "Scene" - - tScene = tChap.child(1) - assert tScene.child(0).text(novelTree.C_TITLE) == "Section" - - ## - # Close - ## - - # qtbot.stopForInteraction() + # qtbot.stop() nwGUI.closeProject() # END Test testGuiNovelTree_TreeItems From 3a061eec3f43f8c253faf2864c1f365efa65045e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:14:28 +0200 Subject: [PATCH 08/10] Update novel tree menu processing and fix issue with reaload of document --- novelwriter/gui/noveltree.py | 159 +++++++++++++++++------------ novelwriter/gui/outline.py | 2 +- novelwriter/gui/projtree.py | 29 ++---- novelwriter/guimain.py | 6 +- tests/test_gui/test_gui_guimain.py | 4 +- tests/test_gui/test_gui_outline.py | 2 +- 6 files changed, 110 insertions(+), 92 deletions(-) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 336058a5..2332bc7c 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -28,11 +28,11 @@ along with this program. If not, see . import logging import novelwriter -from time import time from enum import Enum +from time import time -from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal from PyQt5.QtGui import QPalette, QPixmap, QColor +from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal from PyQt5.QtWidgets import ( QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel, QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, @@ -81,13 +81,7 @@ class GuiNovelView(QWidget): self.setLayout(self.outerBox) - # Connect Signals - self.novelBar.rootFolderSelectionChanged.connect( - lambda tHandle: self.novelTree.refreshTree(rootHandle=tHandle, overRide=True) - ) - # Function Mappings - self.refreshTree = self.novelTree.refreshTree self.updateWordCounts = self.novelTree.updateWordCounts self.getSelectedHandle = self.novelTree.getSelectedHandle @@ -101,30 +95,44 @@ class GuiNovelView(QWidget): self.novelTree.initSettings() return + def refreshTree(self): + """Refresh the current tree. + """ + self.novelTree.refreshTree(rootHandle=self.theProject.lastNovel) + return + def clearProject(self): - self.novelTree.clearTree() + """Clear project-related GUI content. + """ + self.novelTree.clearContent() + self.novelBar.clearContent() return def openProjectTasks(self): - """Run tasks when opening a project. + """Run opening project tasks. """ lastNovel = self.theProject.lastNovel - if lastNovel is None: + if lastNovel not in self.theProject.tree: lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL) logger.debug("Setting novel tree to root item '%s'", lastNovel) + lastCol = self.theProject.options.getEnum( + "GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN + ) + self.clearProject() - self.novelBar.rebuildNovelRootMenu(selHandle=lastNovel) - self.novelTree.loadOptions() - self.novelTree.refreshTree(rootHandle=lastNovel, overRide=True) + self.novelBar.buildNovelRootMenu() + self.novelBar.setLastColType(lastCol, doRefresh=False) + self.novelBar.setCurrentRoot(lastNovel) return def closeProjectTasks(self): - """Run tasks when closing a project. + """Run closing project tasks. """ - self.novelTree.saveOptions() + lastColType = self.novelTree.lastColType + self.theProject.options.setValue("GuiNovelView", "lastCol", lastColType) return def setFocus(self): @@ -133,7 +141,7 @@ class GuiNovelView(QWidget): self.novelTree.setFocus() return - def treeFocus(self): + def treeHasFocus(self): """Check if the novel tree has focus. """ return self.novelTree.hasFocus() @@ -144,9 +152,9 @@ class GuiNovelView(QWidget): @pyqtSlot(str) def updateRootItem(self, tHandle): - """Should be called whenever a root folders changes. + """If any root item changes, rebuild the novel root menu. """ - self.novelBar.rebuildNovelRootMenu() + self.novelBar.buildNovelRootMenu() return # END Class GuiNovelView @@ -154,8 +162,6 @@ class GuiNovelView(QWidget): class GuiNovelToolBar(QWidget): - rootFolderSelectionChanged = pyqtSignal(str) - def __init__(self, novelView): QTreeWidget.__init__(self, novelView) @@ -167,7 +173,7 @@ class GuiNovelToolBar(QWidget): self.mainTheme = novelView.mainGui.mainTheme iPx = self.mainTheme.baseIconSize - mPx = self.mainConf.pxInt(4) + mPx = self.mainConf.pxInt(3) self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) @@ -197,6 +203,8 @@ class GuiNovelToolBar(QWidget): # Novel Root Menu self.mRoot = QMenu() + self.gRoot = QActionGroup(self.mRoot) + self.aRoot = {} self.tbRoot = QToolButton(self) self.tbRoot.setToolTip(self.tr("Novel Root")) @@ -209,19 +217,13 @@ class GuiNovelToolBar(QWidget): # More Options Menu self.mMore = QMenu() - self.mCol3 = self.mMore.addMenu(self.tr("Third Column")) - self.mCol3.addAction(self.tr("Hide Column")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.HIDDEN) - ) - self.mCol3.addAction(self.tr("Point of View Character")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.POV) - ) - self.mCol3.addAction(self.tr("Focus Character")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.FOCUS) - ) - self.mCol3.addAction(self.tr("Novel Plot")).triggered.connect( - lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.PLOT) - ) + self.mLastCol = self.mMore.addMenu(self.tr("Last Column")) + self.gLastCol = QActionGroup(self.mMore) + self.aLastCol = {} + self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden")) + self._addLastColAction(NovelTreeColumn.POV, self.tr("Point of View Character")) + self._addLastColAction(NovelTreeColumn.FOCUS, self.tr("Focus Character")) + self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot")) self.tbMore = QToolButton(self) self.tbMore.setToolTip(self.tr("More Options")) @@ -250,25 +252,41 @@ class GuiNovelToolBar(QWidget): # Methods ## - def rebuildNovelRootMenu(self, selHandle=None): + def clearContent(self): + """Run clearing project tasks. + """ + self.mRoot.clear() + self.aRoot = {} + return + + def buildNovelRootMenu(self): """Build the novel root menu. """ self.mRoot.clear() - agRoot = QActionGroup(self.mRoot) + self.aRoot = {} for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(nwItemClass.NOVEL)): aRoot = self.mRoot.addAction(nwItem.itemName) aRoot.setData(tHandle) aRoot.setCheckable(True) - aRoot.triggered.connect( - lambda n, tHandle=tHandle: self.rootFolderSelectionChanged.emit(tHandle) - ) - agRoot.addAction(aRoot) + aRoot.triggered.connect(lambda n, tHandle=tHandle: self.setCurrentRoot(tHandle)) + self.gRoot.addAction(aRoot) + self.aRoot[tHandle] = aRoot - if n == 0: - aRoot.setChecked(True) - if selHandle == tHandle: - aRoot.setChecked(True) + return + def setCurrentRoot(self, rootHandle): + """Set the current active root handle. + """ + if rootHandle in self.aRoot: + self.aRoot[rootHandle].setChecked(True) + self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) + return + + def setLastColType(self, colType, doRefresh=True): + """Set the last column type. + """ + self.aLastCol[colType].setChecked(True) + self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh) return ## @@ -283,6 +301,20 @@ class GuiNovelToolBar(QWidget): self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) return + ## + # Internal Functions + ## + + def _addLastColAction(self, colType, actionLabel): + """Add a column selection entry to the last column menu. + """ + aLast = self.mLastCol.addAction(actionLabel) + aLast.setCheckable(True) + aLast.setActionGroup(self.gLastCol) + aLast.triggered.connect(lambda: self.setLastColType(colType)) + self.aLastCol[colType] = aLast + return + # END Class GuiNovelToolBar @@ -319,13 +351,12 @@ class GuiNovelTree(QTreeWidget): iPx = self.mainTheme.baseIconSize nPx = self.mainTheme.textNWidth cMg = self.mainConf.pxInt(6) - mPx = self.mainConf.pxInt(4) nMg = self.mainConf.pxInt(6) # self.setIconSize(QSize(iPx, iPx)) self.setFrameStyle(QFrame.NoFrame) self.setHeaderHidden(True) - self.setIndentation(mPx) + self.setIndentation(0) self.setColumnCount(3) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.SingleSelection) @@ -349,7 +380,7 @@ class GuiNovelTree(QTreeWidget): fH2.setBold(True) self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()] - self._hIndent = ["", "", "", "\u2022\u00a0", "\u00bb\u00a0"] + self._hIndent = ["", "", "", "\u203a\u00a0", "\u00bb\u00a0"] self._pIndent = [QPixmap(), QPixmap()] hPix = QPixmap(QSize(iPx, iPx)) @@ -386,11 +417,19 @@ class GuiNovelTree(QTreeWidget): return + ## + # Properties + ## + + @property + def lastColType(self): + return self._lastCol + ## # Class Methods ## - def clearTree(self): + def clearContent(self): """Clear the GUI content and the related maps. """ self.clear() @@ -398,21 +437,6 @@ class GuiNovelTree(QTreeWidget): self._lastBuild = 0 return - def loadOptions(self): - """Load user options. - """ - self._lastCol = self.theProject.options.getEnum( - "GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.POV - ) - self.setColumnHidden(self.C_LAST, self._lastCol == NovelTreeColumn.HIDDEN) - return True - - def saveOptions(self): - """Save user options. - """ - self.theProject.options.setValue("GuiNovelView", "lastCol", self._lastCol) - return - def refreshTree(self, rootHandle=None, overRide=False): """Called whenever the Novel tab is activated. """ @@ -461,14 +485,15 @@ class GuiNovelTree(QTreeWidget): return tHandle, tLine - def setLastColType(self, colType): + def setLastColType(self, colType, doRefresh=True): """Change the content type of the last column and rebuild. """ if self._lastCol != colType: logger.debug("Changing last column to %s", colType.name) self._lastCol = colType self.setColumnHidden(self.C_LAST, colType == NovelTreeColumn.HIDDEN) - self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) + if doRefresh: + self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) return ## @@ -531,7 +556,7 @@ class GuiNovelTree(QTreeWidget): def _populateTree(self, rootHandle): """Build the tree based on the project index. """ - self.clearTree() + self.clearContent() tStart = time() logger.verbose("Building novel tree for root item '%s'", rootHandle) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index c858c480..4b2077f6 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -117,7 +117,7 @@ class GuiOutlineView(QWidget): self.outlineTree.refreshTree(overRide=overRide, novelChanged=novelChanged) return - def treeFocus(self): + def treeHasFocus(self): return self.outlineTree.hasFocus() def setTreeFocus(self): diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 4f320bfc..08b24232 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -31,8 +31,8 @@ import novelwriter from enum import Enum from time import time +from PyQt5.QtGui import QPalette from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot -from PyQt5.QtGui import QIcon, QPalette from PyQt5.QtWidgets import ( QAbstractItemView, QFrame, QHBoxLayout, QHeaderView, QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, @@ -142,7 +142,7 @@ class GuiProjectView(QWidget): self.projTree.setFocus() return - def treeFocus(self): + def treeHasFocus(self): """Check if the project tree has focus. """ return self.projTree.hasFocus() @@ -177,7 +177,7 @@ class GuiProjectToolBar(QWidget): self.mainTheme = projView.mainGui.mainTheme iPx = self.mainTheme.baseIconSize - mPx = self.mainConf.pxInt(4) + mPx = self.mainConf.pxInt(3) self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) @@ -252,7 +252,7 @@ class GuiProjectToolBar(QWidget): self._addRootFolderEntry(nwItemClass.PLOT) self._addRootFolderEntry(nwItemClass.CHARACTER) self._addRootFolderEntry(nwItemClass.WORLD) - self._addRootFolderEntry(nwItemClass.ARCHIVE) + self._addRootFolderEntry(nwItemClass.TIMELINE) self._addRootFolderEntry(nwItemClass.OBJECT) self._addRootFolderEntry(nwItemClass.ENTITY) self._addRootFolderEntry(nwItemClass.CUSTOM) @@ -778,13 +778,6 @@ class GuiProjectTree(QTreeWidget): if trItem is None or nwItem is None: return - expIcon = QIcon() - if nwItem.itemType == nwItemType.FILE: - if nwItem.isExported: - expIcon = self.mainTheme.getIcon("check") - else: - expIcon = self.mainTheme.getIcon("cross") - itemStatus, statusIcon = nwItem.getImportStatus() hLevel = self.theProject.index.getHandleHeaderLevel(tHandle) itemIcon = self.mainTheme.getItemIcon( @@ -793,18 +786,18 @@ class GuiProjectTree(QTreeWidget): trItem.setIcon(self.C_NAME, itemIcon) trItem.setText(self.C_NAME, nwItem.itemName) - trItem.setIcon(self.C_EXPORT, expIcon) trItem.setIcon(self.C_STATUS, statusIcon) trItem.setToolTip(self.C_STATUS, itemStatus) + if nwItem.itemType == nwItemType.FILE: + trItem.setIcon( + self.C_EXPORT, self.mainTheme.getIcon("check" if nwItem.isExported else "cross") + ) + if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT: trFont = trItem.font(self.C_NAME) - if hLevel in ("H1", "H2"): - trFont.setBold(True) - trFont.setUnderline(True) - else: - trFont.setBold(False) - trFont.setUnderline(False) + trFont.setBold(hLevel == "H1" or hLevel == "H2") + trFont.setUnderline(hLevel == "H1") trItem.setFont(self.C_NAME, trFont) return diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 71d99e33..5030347b 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -796,11 +796,11 @@ class GuiMain(QMainWindow): tHandle = None tLine = None - if self.projView.treeFocus(): + if self.projView.treeHasFocus(): tHandle = self.projView.getSelectedHandle() - elif self.novelView.treeFocus(): + elif self.novelView.treeHasFocus(): tHandle, tLine = self.novelView.getSelectedHandle() - elif self.outlineView.treeFocus(): + elif self.outlineView.treeHasFocus(): tHandle, tLine = self.outlineView.getSelectedHandle() else: logger.warning("No item selected") diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index c308451b..5637f5c1 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -135,7 +135,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): nwGUI._changeView(nwView.NOVEL) nwGUI.novelView.refreshTree(rootHandle=None, overRide=True) with monkeypatch.context() as mp: - mp.setattr(GuiNovelView, "treeFocus", lambda *a: True) + mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True) assert nwGUI.docEditor.docHandle() is None selItem = nwGUI.novelView.novelTree.topLevelItem(2) nwGUI.novelView.novelTree.setCurrentItem(selItem) @@ -147,7 +147,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): nwGUI._changeView(nwView.OUTLINE) nwGUI.switchFocus(nwWidget.OUTLINE) with monkeypatch.context() as mp: - mp.setattr(GuiOutlineView, "treeFocus", lambda *a: True) + mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True) assert nwGUI.docEditor.docHandle() is None actItem = nwGUI.outlineView.outlineTree.topLevelItem(0) chpItem = actItem.child(0) diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py index 1038404a..26ca9803 100644 --- a/tests/test_gui/test_gui_outline.py +++ b/tests/test_gui/test_gui_outline.py @@ -71,7 +71,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir): # Check focus with monkeypatch.context() as mp: mp.setattr(QWidget, "hasFocus", lambda *a: True) - assert outlineView.treeFocus() is True + assert outlineView.treeHasFocus() is True outlineView.setTreeFocus() # Can't check. just ensures that it doesn't error From cff464392ae5656eb9fd1d117c161ad781517b25 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:15:29 +0200 Subject: [PATCH 09/10] Use the indenta decorators of the novel tree to indicate type --- .../assets/icons/typicons_dark/icons.conf | 5 +++ .../assets/icons/typicons_dark/nw_deco-h0.svg | 35 +++++++++++++++++++ .../assets/icons/typicons_dark/nw_deco-h1.svg | 35 +++++++++++++++++++ .../assets/icons/typicons_dark/nw_deco-h2.svg | 35 +++++++++++++++++++ .../assets/icons/typicons_dark/nw_deco-h3.svg | 35 +++++++++++++++++++ .../assets/icons/typicons_dark/nw_deco-h4.svg | 35 +++++++++++++++++++ .../assets/icons/typicons_light/icons.conf | 5 +++ .../icons/typicons_light/nw_deco-h0.svg | 35 +++++++++++++++++++ .../icons/typicons_light/nw_deco-h1.svg | 35 +++++++++++++++++++ .../icons/typicons_light/nw_deco-h2.svg | 35 +++++++++++++++++++ .../icons/typicons_light/nw_deco-h3.svg | 35 +++++++++++++++++++ .../icons/typicons_light/nw_deco-h4.svg | 35 +++++++++++++++++++ novelwriter/gui/noveltree.py | 24 ++++++------- novelwriter/gui/theme.py | 22 +++++++----- sample/nwProject.nwx | 16 ++++----- tests/test_gui/test_gui_theme.py | 2 +- 16 files changed, 393 insertions(+), 31 deletions(-) create mode 100644 novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg create mode 100644 novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg create mode 100644 novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg create mode 100644 novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg create mode 100644 novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg create mode 100644 novelwriter/assets/icons/typicons_light/nw_deco-h0.svg create mode 100644 novelwriter/assets/icons/typicons_light/nw_deco-h1.svg create mode 100644 novelwriter/assets/icons/typicons_light/nw_deco-h2.svg create mode 100644 novelwriter/assets/icons/typicons_light/nw_deco-h3.svg create mode 100644 novelwriter/assets/icons/typicons_light/nw_deco-h4.svg diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf index ae157e2c..d4106803 100644 --- a/novelwriter/assets/icons/typicons_dark/icons.conf +++ b/novelwriter/assets/icons/typicons_dark/icons.conf @@ -83,3 +83,8 @@ view_build = typ_export.svg view_editor = mixed_edit.svg view_novel = typ_book-grey.svg view_outline = typ_puzzle-outline.svg + +deco_doc_h1 = nw_deco-h1.svg +deco_doc_h2 = nw_deco-h2.svg +deco_doc_h3 = nw_deco-h3.svg +deco_doc_h4 = nw_deco-h4.svg diff --git a/novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg b/novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg new file mode 100644 index 00000000..3c1618c9 --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg b/novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg new file mode 100644 index 00000000..1c0dec9b --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg b/novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg new file mode 100644 index 00000000..0f86e5bb --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg b/novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg new file mode 100644 index 00000000..f05e46e6 --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg b/novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg new file mode 100644 index 00000000..aa74e6f3 --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf index 4683bb19..8d267a47 100644 --- a/novelwriter/assets/icons/typicons_light/icons.conf +++ b/novelwriter/assets/icons/typicons_light/icons.conf @@ -83,3 +83,8 @@ view_build = typ_export.svg view_editor = mixed_edit.svg view_novel = typ_book-grey.svg view_outline = typ_puzzle-outline.svg + +deco_doc_h1 = nw_deco-h1.svg +deco_doc_h2 = nw_deco-h2.svg +deco_doc_h3 = nw_deco-h3.svg +deco_doc_h4 = nw_deco-h4.svg diff --git a/novelwriter/assets/icons/typicons_light/nw_deco-h0.svg b/novelwriter/assets/icons/typicons_light/nw_deco-h0.svg new file mode 100644 index 00000000..3c1618c9 --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_deco-h0.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/nw_deco-h1.svg b/novelwriter/assets/icons/typicons_light/nw_deco-h1.svg new file mode 100644 index 00000000..e6c8efdc --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_deco-h1.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/nw_deco-h2.svg b/novelwriter/assets/icons/typicons_light/nw_deco-h2.svg new file mode 100644 index 00000000..7caa4203 --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_deco-h2.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/nw_deco-h3.svg b/novelwriter/assets/icons/typicons_light/nw_deco-h3.svg new file mode 100644 index 00000000..61feca1b --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_deco-h3.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/nw_deco-h4.svg b/novelwriter/assets/icons/typicons_light/nw_deco-h4.svg new file mode 100644 index 00000000..b76fd7da --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_deco-h4.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 2332bc7c..8711dc8d 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -31,7 +31,7 @@ import novelwriter from enum import Enum from time import time -from PyQt5.QtGui import QPalette, QPixmap, QColor +from PyQt5.QtGui import QPalette from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal from PyQt5.QtWidgets import ( QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel, @@ -349,11 +349,9 @@ class GuiNovelTree(QTreeWidget): # ========= iPx = self.mainTheme.baseIconSize - nPx = self.mainTheme.textNWidth cMg = self.mainConf.pxInt(6) - nMg = self.mainConf.pxInt(6) - # self.setIconSize(QSize(iPx, iPx)) + self.setIconSize(QSize(iPx, iPx)) self.setFrameStyle(QFrame.NoFrame) self.setHeaderHidden(True) self.setIndentation(0) @@ -380,15 +378,13 @@ class GuiNovelTree(QTreeWidget): fH2.setBold(True) self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()] - self._hIndent = ["", "", "", "\u203a\u00a0", "\u00bb\u00a0"] - self._pIndent = [QPixmap(), QPixmap()] - - hPix = QPixmap(QSize(iPx, iPx)) - hPix.fill(QColor(0, 0, 0, 0)) - for m in range(1, 4): - self._pIndent.append(hPix.scaled( - max(nPx*m - nMg, nMg), 2, Qt.IgnoreAspectRatio, Qt.FastTransformation - )) + self._pIndent = [ + self.mainTheme.loadDecoration("deco_doc_h0", pxH=iPx), + self.mainTheme.loadDecoration("deco_doc_h1", pxH=iPx), + self.mainTheme.loadDecoration("deco_doc_h2", pxH=iPx), + self.mainTheme.loadDecoration("deco_doc_h3", pxH=iPx), + self.mainTheme.loadDecoration("deco_doc_h4", pxH=iPx), + ] # Connect signals self.itemDoubleClicked.connect(self._treeDoubleClick) @@ -571,7 +567,7 @@ class GuiNovelTree(QTreeWidget): theData = (tHandle, sTitle[1:].lstrip("0"), tKey) newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel]) - newItem.setText(self.C_TITLE, self._hIndent[iLevel] + novIdx.title) + newItem.setText(self.C_TITLE, novIdx.title) newItem.setData(self.C_TITLE, Qt.UserRole, theData) newItem.setFont(self.C_TITLE, self._hFonts[iLevel]) newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}") diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 3a59e7e2..6c4ff50e 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -472,9 +472,12 @@ class GuiIcons: # Switches "sticky-on", "sticky-off", "bullet-on", "bullet-off", + + # Decorations + "deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", } - DECO_MAP = { + IMAGE_MAP = { "wiz-back": "wizard-back.jpg", } @@ -573,19 +576,22 @@ class GuiIcons: # Access Functions ## - def loadDecoration(self, decoKey, pxW, pxH): + def loadDecoration(self, decoKey, pxW=None, pxH=None): """Load graphical decoration element based on the decoration - map. This function always returns a QSwgWidget. + map or the icon map. This function always returns a QPixmap. """ - if decoKey not in self.DECO_MAP: + if decoKey in self._themeMap: + imgPath = self._themeMap[decoKey] + elif decoKey in self.IMAGE_MAP: + imgPath = os.path.join( + self.mainConf.assetPath, "images", self.IMAGE_MAP[decoKey] + ) + else: logger.error("Decoration with name '%s' does not exist", decoKey) return QPixmap() - imgPath = os.path.join( - self.mainConf.assetPath, "images", self.DECO_MAP[decoKey] - ) if not os.path.isfile(imgPath): - logger.error("Decoration file '%s' not in assets folder", self.DECO_MAP[decoKey]) + logger.error("Asset '%s' not found", self.IMAGE_MAP[decoKey]) return QPixmap() theDeco = QPixmap(imgPath) diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index af509400..6ba4b5f1 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1367 - 231 - 68784 + 1371 + 236 + 69222 False @@ -35,9 +35,9 @@
- New + New Notes - Started + Started 1st Draft 2nd Draft 3rd Draft @@ -92,7 +92,7 @@ Chapter Two
- + We Found John! @@ -101,7 +101,7 @@ - Title Page + Title Page diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 756b2a81..8c7b934a 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -127,7 +127,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir): assert anImg.isNull() # Add a non-existent file and request it - iconCache.DECO_MAP["nonsense"] = "nofile.jpg" + iconCache.IMAGE_MAP["nonsense"] = "nofile.jpg" anImg = iconCache.loadDecoration("nonsense", 20, 20) assert isinstance(anImg, QPixmap) assert anImg.isNull() From 6ccbb358b916aedff1c63b6bec92001e218ad8fc Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:46:44 +0200 Subject: [PATCH 10/10] Update tests --- tests/test_gui/test_gui_guimain.py | 2 +- tests/test_gui/test_gui_noveltree.py | 69 +++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 5637f5c1..6407b84b 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -133,7 +133,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): # Novel Tree has focus nwGUI._changeView(nwView.NOVEL) - nwGUI.novelView.refreshTree(rootHandle=None, overRide=True) + nwGUI.novelView.novelTree.refreshTree(rootHandle=None, overRide=True) with monkeypatch.context() as mp: mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True) assert nwGUI.docEditor.docHandle() is None diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index ea8b49bc..102b03d2 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -19,23 +19,47 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import os import pytest +from tools import buildTestProject, writeFile + from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMessageBox +from novelwriter.enum import nwWidget, nwItemType +from novelwriter.dialogs import GuiEditLabel +from novelwriter.gui.noveltree import NovelTreeColumn + @pytest.mark.gui -def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): +def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test navigating the novel tree. """ # Block message box monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) + monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) + + buildTestProject(nwGUI, fncProj) + + nwGUI.switchFocus(nwWidget.TREE) + nwGUI.projView.projTree.clearSelection() + nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True) + nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) + + writeFile( + os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"), + "# Jane Doe\n\n@tag: Jane\n\n" + ) + writeFile( + os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"), + "### Scene One\n\n@pov: Jane\n@focus: Jane\n\n" + ) - nwGUI.openProject(nwMinimal) novelView = nwGUI.novelView novelTree = novelView.novelTree + novelBar = novelView.novelBar # Show/Hide Scrollbars # ==================== @@ -65,9 +89,10 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): assert not topItem.isSelected() topItem.setSelected(True) assert novelTree.selectedItems()[0] == topItem - assert novelView.getSelectedHandle() == ("a35baf2e93843", 0) + assert novelView.getSelectedHandle() == ("000000000000c", 0) - novelView.refreshTree() + # Refresh using the slot for the butoom + novelBar._refreshNovelTree() assert novelTree.topLevelItem(0).isSelected() # Open Items @@ -89,7 +114,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): assert scItem.isSelected() assert nwGUI.docEditor.docHandle() is None novelTree._treeDoubleClick(scItem, 0) - assert nwGUI.docEditor.docHandle() == "8c659a11cd429" + assert nwGUI.docEditor.docHandle() == "000000000000f" # Open item with middle mouse button scItem.setSelected(True) @@ -106,7 +131,39 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): scItem.setData(novelTree.C_TITLE, Qt.UserRole, oldData) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) - assert nwGUI.docViewer.docHandle() == "8c659a11cd429" + assert nwGUI.docViewer.docHandle() == "000000000000f" + + # Last Column + # =========== + + novelBar.setLastColType(NovelTreeColumn.HIDDEN) + assert novelTree.isColumnHidden(novelTree.C_LAST) is True + assert novelTree.lastColType == NovelTreeColumn.HIDDEN + assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "") + + novelBar.setLastColType(NovelTreeColumn.POV) + assert novelTree.isColumnHidden(novelTree.C_LAST) is False + assert novelTree.lastColType == NovelTreeColumn.POV + assert novelTree._getLastColumnText("000000000000f", "T000001") == ( + "Jane", "Point of View: Jane" + ) + + novelBar.setLastColType(NovelTreeColumn.FOCUS) + assert novelTree.isColumnHidden(novelTree.C_LAST) is False + assert novelTree.lastColType == NovelTreeColumn.FOCUS + assert novelTree._getLastColumnText("000000000000f", "T000001") == ( + "Jane", "Focus: Jane" + ) + + novelBar.setLastColType(NovelTreeColumn.PLOT) + assert novelTree.isColumnHidden(novelTree.C_LAST) is False + assert novelTree.lastColType == NovelTreeColumn.PLOT + assert novelTree._getLastColumnText("000000000000f", "T000001") == ( + "", "Plot: " + ) + + novelTree._lastCol = None + assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "") # Close # =====