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