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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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
# =====