Refactor and clean up GUI code (#2078)

This commit is contained in:
Veronica Berglyd Olsen
2024-10-31 18:42:32 +01:00
committed by GitHub
14 changed files with 264 additions and 363 deletions
+1 -1
View File
@@ -436,7 +436,7 @@ def describeFont(font: QFont) -> str:
def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
"""A replacement for Python lambdas that works for Qt slots."""
def wrapper(*args_: Any, **kwargs_: Any) -> None:
def wrapper(*a_: Any) -> None:
func(*args, **kwargs)
return wrapper
+3
View File
@@ -73,6 +73,9 @@ VALID_MAP: dict[str, set[str]] = {
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble",
"novelRoot",
},
"GuiOutlineDetails": {
"detailsWidth", "tagsWidth",
}
}
+3 -3
View File
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
from novelwriter.common import qtLambda, simplified
from novelwriter.constants import nwLabels, trConst
from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
@@ -365,10 +365,10 @@ class _StatusPage(NFixedPage):
self.delButton.clicked.connect(self._delItem)
self.upButton = NIconToolButton(self, iSz, "up")
self.upButton.clicked.connect(lambda: self._moveItem(-1))
self.upButton.clicked.connect(qtLambda(self._moveItem, -1))
self.dnButton = NIconToolButton(self, iSz, "down")
self.dnButton.clicked.connect(lambda: self._moveItem(1))
self.dnButton.clicked.connect(qtLambda(self._moveItem, 1))
# Edit Form
self.editName = QLineEdit(self)
+9 -8
View File
@@ -38,6 +38,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
from novelwriter.constants import nwStyles, nwUnicode
from novelwriter.enum import nwDocAction, nwDocMode, nwItemType
from novelwriter.error import logException
@@ -396,25 +397,25 @@ class GuiDocViewer(QTextBrowser):
if userSelection:
mnuCopy = QAction(self.tr("Copy"), ctxMenu)
mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
mnuCopy.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY))
ctxMenu.addAction(mnuCopy)
ctxMenu.addSeparator()
mnuSelAll = QAction(self.tr("Select All"), ctxMenu)
mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
mnuSelAll.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL))
ctxMenu.addAction(mnuSelAll)
mnuSelWord = QAction(self.tr("Select Word"), ctxMenu)
mnuSelWord.triggered.connect(
lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, point)
)
mnuSelWord.triggered.connect(qtLambda(
self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, point
))
ctxMenu.addAction(mnuSelWord)
mnuSelPara = QAction(self.tr("Select Paragraph"), ctxMenu)
mnuSelPara.triggered.connect(
lambda: self._makePosSelection(QTextCursor.SelectionType.BlockUnderCursor, point)
)
mnuSelPara.triggered.connect(qtLambda(
self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, point
))
ctxMenu.addAction(mnuSelPara)
# Open the context menu
+8 -4
View File
@@ -30,7 +30,7 @@ from PyQt5.QtWidgets import QGridLayout, QLabel, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.common import elide
from novelwriter.constants import nwLabels, trConst
from novelwriter.constants import nwLabels, nwStats, trConst
from novelwriter.types import (
QtAlignLeft, QtAlignLeftBase, QtAlignRight, QtAlignRightBase,
QtAlignRightMiddle
@@ -62,6 +62,10 @@ class GuiItemDetails(QWidget):
fntValue = self.font()
fntValue.setPointSizeF(0.9*fPt)
trStats1 = trConst(nwLabels.STATS_NAME[nwStats.CHARS_ALL])
trStats2 = trConst(nwLabels.STATS_NAME[nwStats.WORDS_ALL])
trStats3 = trConst(nwLabels.STATS_NAME[nwStats.PARAGRAPHS])
# Label
self.labelName = QLabel(self.tr("Label"), self)
self.labelName.setFont(fntLabel)
@@ -113,7 +117,7 @@ class GuiItemDetails(QWidget):
self.usageData.setWordWrap(True)
# Character Count
self.cCountName = QLabel(" "+self.tr("Characters"), self)
self.cCountName = QLabel(trStats1, self)
self.cCountName.setFont(fntLabel)
self.cCountName.setAlignment(QtAlignRight)
@@ -122,7 +126,7 @@ class GuiItemDetails(QWidget):
self.cCountData.setAlignment(QtAlignRight)
# Word Count
self.wCountName = QLabel(" "+self.tr("Words"), self)
self.wCountName = QLabel(trStats2, self)
self.wCountName.setFont(fntLabel)
self.wCountName.setAlignment(QtAlignRight)
@@ -131,7 +135,7 @@ class GuiItemDetails(QWidget):
self.wCountData.setAlignment(QtAlignRight)
# Paragraph Count
self.pCountName = QLabel(" "+self.tr("Paragraphs"), self)
self.pCountName = QLabel(trStats3, self)
self.pCountName.setFont(fntLabel)
self.pCountName.setAlignment(QtAlignRight)
+4 -11
View File
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax
from novelwriter.common import minmax, qtLambda
from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.core.index import IndexHeading
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
@@ -346,7 +346,7 @@ class GuiNovelToolBar(QWidget):
aLast = self.mLastCol.addAction(actionLabel)
aLast.setCheckable(True)
aLast.setActionGroup(self.gLastCol)
aLast.triggered.connect(lambda: self.setLastColType(colType))
aLast.triggered.connect(qtLambda(self.setLastColType, colType))
self.aLastCol[colType] = aLast
return
@@ -484,16 +484,9 @@ class GuiNovelTree(QTreeWidget):
if rootHandle is None:
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
treeChanged = SHARED.mainGui.projView.changedSince(self._lastBuild)
indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild)
if not (treeChanged or indexChanged or overRide):
logger.debug("No changes have been made to the novel index")
return
selItem = self.selectedItems()
titleKey = None
if selItem:
titleKey = selItem[0].data(self.C_DATA, self.D_KEY)
if selItems := self.selectedItems():
titleKey = selItems[0].data(self.C_DATA, self.D_KEY)
self._populateTree(rootHandle)
SHARED.project.data.setLastHandle(rootHandle, "novelTree")
+73 -170
View File
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.constants import nwKeyWords, nwLabels, nwStats, nwStyles, trConst
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
from novelwriter.error import logException
from novelwriter.extensions.configlayout import NColourLabel
@@ -135,12 +135,14 @@ class GuiOutlineView(QWidget):
self.outlineBar.populateNovelList()
self.outlineBar.setCurrentRoot(lastOutline)
self.outlineBar.setEnabled(True)
self.outlineData.loadGuiSettings()
return
def closeProjectTasks(self) -> None:
"""Run closing project tasks."""
self.outlineTree.closeProjectTasks()
self.outlineData.saveGuiSettings()
self.outlineData.updateClasses()
self.clearOutline()
return
@@ -799,6 +801,10 @@ class GuiOutlineDetails(QScrollArea):
bFont = SHARED.theme.guiFontB
trStats1 = trConst(nwLabels.STATS_NAME[nwStats.CHARS_ALL])
trStats2 = trConst(nwLabels.STATS_NAME[nwStats.WORDS_ALL])
trStats3 = trConst(nwLabels.STATS_NAME[nwStats.PARAGRAPHS])
# Details Area
self.titleLabel = QLabel(self.tr("Title"), self)
self.fileLabel = QLabel(self.tr("Document"), self)
@@ -819,9 +825,9 @@ class GuiOutlineDetails(QScrollArea):
self.itemValue.setMaximumWidth(maxTitle)
# Stats Area
self.cCLabel = QLabel(self.tr("Characters"), self)
self.wCLabel = QLabel(self.tr("Words"), self)
self.pCLabel = QLabel(self.tr("Paragraphs"), self)
self.cCLabel = QLabel(trStats1, self)
self.wCLabel = QLabel(trStats2, self)
self.pCLabel = QLabel(trStats3, self)
self.cCValue = QLabel("", self)
self.wCValue = QLabel("", self)
self.pCValue = QLabel("", self)
@@ -848,94 +854,9 @@ class GuiOutlineDetails(QScrollArea):
self.synopLWrap = QHBoxLayout()
self.synopLWrap.addWidget(self.synopValue, 1)
# Tags
self.povKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]), self)
self.focKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]), self)
self.chrKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]), self)
self.pltKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]), self)
self.timKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]), self)
self.wldKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]), self)
self.objKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]), self)
self.entKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]), self)
self.cstKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]), self)
self.styKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.STORY_KEY]), self)
self.mntKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.MENTION_KEY]), self)
self.povKeyLabel.setFont(bFont)
self.focKeyLabel.setFont(bFont)
self.chrKeyLabel.setFont(bFont)
self.pltKeyLabel.setFont(bFont)
self.timKeyLabel.setFont(bFont)
self.wldKeyLabel.setFont(bFont)
self.objKeyLabel.setFont(bFont)
self.entKeyLabel.setFont(bFont)
self.cstKeyLabel.setFont(bFont)
self.styKeyLabel.setFont(bFont)
self.mntKeyLabel.setFont(bFont)
self.povKeyLWrap = QHBoxLayout()
self.focKeyLWrap = QHBoxLayout()
self.chrKeyLWrap = QHBoxLayout()
self.pltKeyLWrap = QHBoxLayout()
self.timKeyLWrap = QHBoxLayout()
self.wldKeyLWrap = QHBoxLayout()
self.objKeyLWrap = QHBoxLayout()
self.entKeyLWrap = QHBoxLayout()
self.cstKeyLWrap = QHBoxLayout()
self.styKeyLWrap = QHBoxLayout()
self.mntKeyLWrap = QHBoxLayout()
self.povKeyValue = QLabel("", self)
self.focKeyValue = QLabel("", self)
self.chrKeyValue = QLabel("", self)
self.pltKeyValue = QLabel("", self)
self.timKeyValue = QLabel("", self)
self.wldKeyValue = QLabel("", self)
self.objKeyValue = QLabel("", self)
self.entKeyValue = QLabel("", self)
self.cstKeyValue = QLabel("", self)
self.styKeyValue = QLabel("", self)
self.mntKeyValue = QLabel("", self)
self.povKeyValue.setWordWrap(True)
self.focKeyValue.setWordWrap(True)
self.chrKeyValue.setWordWrap(True)
self.pltKeyValue.setWordWrap(True)
self.timKeyValue.setWordWrap(True)
self.wldKeyValue.setWordWrap(True)
self.objKeyValue.setWordWrap(True)
self.entKeyValue.setWordWrap(True)
self.cstKeyValue.setWordWrap(True)
self.styKeyValue.setWordWrap(True)
self.mntKeyValue.setWordWrap(True)
self.povKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.focKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.chrKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.pltKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.timKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.wldKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.objKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.entKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.cstKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.styKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.mntKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.povKeyLWrap.addWidget(self.povKeyValue, 1)
self.focKeyLWrap.addWidget(self.focKeyValue, 1)
self.chrKeyLWrap.addWidget(self.chrKeyValue, 1)
self.pltKeyLWrap.addWidget(self.pltKeyValue, 1)
self.timKeyLWrap.addWidget(self.timKeyValue, 1)
self.wldKeyLWrap.addWidget(self.wldKeyValue, 1)
self.objKeyLWrap.addWidget(self.objKeyValue, 1)
self.entKeyLWrap.addWidget(self.entKeyValue, 1)
self.cstKeyLWrap.addWidget(self.cstKeyValue, 1)
self.styKeyLWrap.addWidget(self.styKeyValue, 1)
self.mntKeyLWrap.addWidget(self.mntKeyValue, 1)
# Selected Item Details
self.mainGroup = QGroupBox(self.tr("Title Details"), self)
self.mainForm = QGridLayout()
self.mainGroup = QGroupBox(self.tr("Title Details"), self)
self.mainGroup.setLayout(self.mainForm)
self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, QtAlignLeftTop)
@@ -959,46 +880,37 @@ class GuiOutlineDetails(QScrollArea):
self.mainForm.setVerticalSpacing(vSpace)
# Selected Item Tags
self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
self.tagsForm = QGridLayout()
self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
self.tagsGroup.setLayout(self.tagsForm)
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.focKeyLabel, 1, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.timKeyLabel, 4, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.objKeyLabel, 6, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.entKeyLabel, 7, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.styKeyLabel, 9, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.styKeyLWrap, 9, 1, QtAlignLeftTop)
self.tagsForm.addWidget(self.mntKeyLabel, 10, 0, QtAlignLeftTop)
self.tagsForm.addLayout(self.mntKeyLWrap, 10, 1, QtAlignLeftTop)
self.tagValues: dict[str, tuple[QLabel, QLabel]] = {}
for key in nwKeyWords.ALL_KEYS:
label = QLabel(trConst(nwLabels.KEY_NAME[key]), self)
label.setFont(bFont)
value = QLabel("", self)
value.setWordWrap(True)
value.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
layout = QHBoxLayout()
layout.addWidget(value, 1)
n = len(self.tagValues)
self.tagsForm.addWidget(label, n, 0, QtAlignLeftTop)
self.tagsForm.addLayout(layout, n, 1, QtAlignLeftTop)
self.tagValues[key] = (label, value)
self.tagsForm.setColumnStretch(1, 1)
self.tagsForm.setRowStretch(10, 1)
self.tagsForm.setRowStretch(len(self.tagValues), 1)
self.tagsForm.setHorizontalSpacing(hSpace)
self.tagsForm.setVerticalSpacing(vSpace)
# Assemble
self.outerWidget = QWidget(self)
self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.mainGroup, 0)
self.outerBox.addWidget(self.tagsGroup, 1)
self.mainSplit = QSplitter(Qt.Orientation.Horizontal)
self.mainSplit.addWidget(self.mainGroup)
self.mainSplit.addWidget(self.tagsGroup)
self.mainSplit.setCollapsible(0, False)
self.mainSplit.setCollapsible(1, False)
self.outerWidget.setLayout(self.outerBox)
self.setWidget(self.outerWidget)
self.setWidget(self.mainSplit)
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
@@ -1024,27 +936,42 @@ class GuiOutlineDetails(QScrollArea):
self.updateClasses()
return
def loadGuiSettings(self) -> None:
"""Run open project tasks."""
half = self.width() // 2
pOptions = SHARED.project.options
self.mainSplit.setSizes([
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", half)),
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", half))
])
return
def saveGuiSettings(self) -> None:
"""Run close project tasks."""
mainSplit = self.mainSplit.sizes()
detailsWidth = CONFIG.rpxInt(mainSplit[0])
tagsWidth = CONFIG.rpxInt(mainSplit[1])
logger.debug("Saving State: GuiOutlineDetails")
pOptions = SHARED.project.options
pOptions.setValue("GuiOutlineDetails", "detailsWidth", detailsWidth)
pOptions.setValue("GuiOutlineDetails", "tagsWidth", tagsWidth)
return
def clearDetails(self) -> None:
"""Clear all the data labels."""
self.titleLabel.setText(self.tr("Title"))
self.titleValue.setText("")
self.fileValue.setText("")
self.itemValue.setText("")
self.cCValue.setText("")
self.wCValue.setText("")
self.pCValue.setText("")
self.synopValue.setText("")
self.povKeyValue.setText("")
self.focKeyValue.setText("")
self.chrKeyValue.setText("")
self.pltKeyValue.setText("")
self.timKeyValue.setText("")
self.wldKeyValue.setText("")
self.objKeyValue.setText("")
self.entKeyValue.setText("")
self.cstKeyValue.setText("")
self.styKeyValue.setText("")
self.mntKeyValue.setText("")
self.titleValue.clear()
self.fileValue.clear()
self.itemValue.clear()
self.cCValue.clear()
self.wCValue.clear()
self.pCValue.clear()
self.synopValue.clear()
for _, value in self.tagValues.values():
value.clear()
self.updateClasses()
return
@@ -1076,17 +1003,8 @@ class GuiOutlineDetails(QScrollArea):
self.synopValue.setText(novIdx.synopsis)
self.povKeyValue.setText(self._formatTags(novRefs, nwKeyWords.POV_KEY))
self.focKeyValue.setText(self._formatTags(novRefs, nwKeyWords.FOCUS_KEY))
self.chrKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CHAR_KEY))
self.pltKeyValue.setText(self._formatTags(novRefs, nwKeyWords.PLOT_KEY))
self.timKeyValue.setText(self._formatTags(novRefs, nwKeyWords.TIME_KEY))
self.wldKeyValue.setText(self._formatTags(novRefs, nwKeyWords.WORLD_KEY))
self.objKeyValue.setText(self._formatTags(novRefs, nwKeyWords.OBJECT_KEY))
self.entKeyValue.setText(self._formatTags(novRefs, nwKeyWords.ENTITY_KEY))
self.cstKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CUSTOM_KEY))
self.styKeyValue.setText(self._formatTags(novRefs, nwKeyWords.STORY_KEY))
self.mntKeyValue.setText(self._formatTags(novRefs, nwKeyWords.MENTION_KEY))
for key, (_, value) in self.tagValues.items():
value.setText(self._formatTags(novRefs, key))
return
@@ -1094,27 +1012,12 @@ class GuiOutlineDetails(QScrollArea):
def updateClasses(self) -> None:
"""Update the visibility status of class details."""
usedClasses = SHARED.project.tree.rootClasses()
pltVisible = nwItemClass.PLOT in usedClasses
timVisible = nwItemClass.TIMELINE in usedClasses
wldVisible = nwItemClass.WORLD in usedClasses
objVisible = nwItemClass.OBJECT in usedClasses
entVisible = nwItemClass.ENTITY in usedClasses
cstVisible = nwItemClass.CUSTOM in usedClasses
self.pltKeyLabel.setVisible(pltVisible)
self.pltKeyValue.setVisible(pltVisible)
self.timKeyLabel.setVisible(timVisible)
self.timKeyValue.setVisible(timVisible)
self.wldKeyLabel.setVisible(wldVisible)
self.wldKeyValue.setVisible(wldVisible)
self.objKeyLabel.setVisible(objVisible)
self.objKeyValue.setVisible(objVisible)
self.entKeyLabel.setVisible(entVisible)
self.entKeyValue.setVisible(entVisible)
self.cstKeyLabel.setVisible(cstVisible)
self.cstKeyValue.setVisible(cstVisible)
for key, itemClass in nwKeyWords.KEY_CLASS.items():
visible = itemClass in usedClasses
if key in self.tagValues:
label, value = self.tagValues[key]
label.setVisible(visible)
value.setVisible(visible)
return
@staticmethod
+115 -124
View File
@@ -29,7 +29,6 @@ from __future__ import annotations
import logging
from enum import Enum
from time import time
from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
@@ -39,7 +38,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax
from novelwriter.common import minmax, qtLambda
from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
from novelwriter.core.item import NWItem
@@ -97,37 +96,37 @@ class GuiProjectView(QWidget):
self.keyMoveUp = QShortcut(self.projTree)
self.keyMoveUp.setKey("Ctrl+Up")
self.keyMoveUp.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyMoveUp.activated.connect(lambda: self.projTree.moveTreeItem(-1))
self.keyMoveUp.activated.connect(qtLambda(self.projTree.moveTreeItem, -1))
self.keyMoveDn = QShortcut(self.projTree)
self.keyMoveDn.setKey("Ctrl+Down")
self.keyMoveDn.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyMoveDn.activated.connect(lambda: self.projTree.moveTreeItem(1))
self.keyMoveDn.activated.connect(qtLambda(self.projTree.moveTreeItem, 1))
self.keyGoPrev = QShortcut(self.projTree)
self.keyGoPrev.setKey("Alt+Up")
self.keyGoPrev.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyGoPrev.activated.connect(lambda: self.projTree.moveToNextItem(-1))
self.keyGoPrev.activated.connect(qtLambda(self.projTree.moveToNextItem, -1))
self.keyGoNext = QShortcut(self.projTree)
self.keyGoNext.setKey("Alt+Down")
self.keyGoNext.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyGoNext.activated.connect(lambda: self.projTree.moveToNextItem(1))
self.keyGoNext.activated.connect(qtLambda(self.projTree.moveToNextItem, 1))
self.keyGoUp = QShortcut(self.projTree)
self.keyGoUp.setKey("Alt+Left")
self.keyGoUp.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyGoUp.activated.connect(lambda: self.projTree.moveToLevel(-1))
self.keyGoUp.activated.connect(qtLambda(self.projTree.moveToLevel, -1))
self.keyGoDown = QShortcut(self.projTree)
self.keyGoDown.setKey("Alt+Right")
self.keyGoDown.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyGoDown.activated.connect(lambda: self.projTree.moveToLevel(1))
self.keyGoDown.activated.connect(qtLambda(self.projTree.moveToLevel, 1))
self.keyContext = QShortcut(self.projTree)
self.keyContext.setKey("Ctrl+.")
self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
self.keyContext.activated.connect(self.projTree.openContextMenu)
# Signals
self.selectedItemChanged.connect(self.projBar.treeSelectionChanged)
@@ -138,7 +137,6 @@ class GuiProjectView(QWidget):
self.emptyTrash = self.projTree.emptyTrash
self.requestDeleteItem = self.projTree.requestDeleteItem
self.getSelectedHandle = self.projTree.getSelectedHandle
self.changedSince = self.projTree.changedSince
return
@@ -292,38 +290,38 @@ class GuiProjectToolBar(QWidget):
# Move Buttons
self.tbMoveU = NIconToolButton(self, iSz)
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
self.tbMoveU.clicked.connect(qtLambda(self.projTree.moveTreeItem, -1))
self.tbMoveD = NIconToolButton(self, iSz)
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
self.tbMoveD.clicked.connect(qtLambda(self.projTree.moveTreeItem, 1))
# Add Item Menu
self.mAdd = QMenu(self)
self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"]))
self.aAddEmpty.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=0, isNote=False)
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=0, isNote=False)
)
self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"]))
self.aAddChap.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=2, isNote=False)
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=2, isNote=False)
)
self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"]))
self.aAddScene.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=3, isNote=False)
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=3, isNote=False)
)
self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"]))
self.aAddNote.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=1, isNote=True)
)
self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"]))
self.aAddFolder.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FOLDER)
qtLambda(self.projTree.newTreeItem, nwItemType.FOLDER)
)
self.mTemplates = _UpdatableMenu(self.mAdd)
@@ -343,13 +341,17 @@ class GuiProjectToolBar(QWidget):
self.mMore = QMenu(self)
self.aExpand = self.mMore.addAction(self.tr("Expand All"))
self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True))
self.aExpand.triggered.connect(
qtLambda(self.projTree.setExpandedFromHandle, None, True)
)
self.aCollapse = self.mMore.addAction(self.tr("Collapse All"))
self.aCollapse.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, False))
self.aCollapse.triggered.connect(
qtLambda(self.projTree.setExpandedFromHandle, None, False)
)
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
self.aEmptyTrash.triggered.connect(qtLambda(self.projTree.emptyTrash))
self.tbMore = NIconToolButton(self, iSz)
self.tbMore.setToolTip(self.tr("More Options"))
@@ -422,7 +424,7 @@ class GuiProjectToolBar(QWidget):
action.setData(tHandle)
action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
action.triggered.connect(
lambda _, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True)
qtLambda(self.projView.setSelectedHandle, tHandle, doScroll=True)
)
return
@@ -461,7 +463,9 @@ class GuiProjectToolBar(QWidget):
def addClass(itemClass: nwItemClass) -> None:
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
aNew.triggered.connect(
qtLambda(self.projTree.newTreeItem, nwItemType.ROOT, itemClass)
)
self.mAddRoot.addAction(aNew)
return
@@ -504,7 +508,6 @@ class GuiProjectTree(QTreeWidget):
# Internal Variables
self._treeMap: dict[str, QTreeWidgetItem] = {}
self._timeChanged = 0.0
self._popAlert = None
self._actHandle = None
@@ -518,7 +521,7 @@ class GuiProjectTree(QTreeWidget):
# Context Menu
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu)
self.customContextMenuRequested.connect(self.openContextMenu)
# Tree Settings
iPx = SHARED.theme.baseIconHeight
@@ -602,7 +605,6 @@ class GuiProjectTree(QTreeWidget):
"""Clear the GUI content and the related map."""
self.clear()
self._treeMap = {}
self._timeChanged = 0.0
return
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
@@ -1121,12 +1123,10 @@ class GuiProjectTree(QTreeWidget):
"""
logger.debug("Building the project tree ...")
self.clearTree()
count = 0
for nwItem in SHARED.project.iterProjectItems():
count += 1
self._addTreeItem(nwItem)
if count > 0:
logger.info("%d item(s) added to the project tree", count)
self.setActiveHandle(self._actHandle)
logger.info("%d item(s) added to the project tree", len(self._treeMap))
return
def getSelectedHandle(self) -> str | None:
@@ -1137,19 +1137,13 @@ class GuiProjectTree(QTreeWidget):
return items[0].data(self.C_DATA, self.D_HANDLE)
return None
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> bool:
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
"""Set a specific handle as the selected item."""
tItem = self._getTreeItem(tHandle)
if tItem is None:
return False
if tHandle in self._treeMap:
self.setCurrentItem(self._treeMap[tHandle])
if (indexes := self.selectedIndexes()) and doScroll:
self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
return True
if (indexes := self.selectedIndexes()) and doScroll:
self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
return
def setActiveHandle(self, tHandle: str | None) -> None:
"""Highlight the rows associated with a given handle."""
@@ -1169,19 +1163,52 @@ class GuiProjectTree(QTreeWidget):
status for all child items. If tHandle is None, it affects the
entire tree.
"""
trItem = self._getTreeItem(tHandle) or self.invisibleRootItem()
self._recursiveSetExpanded(trItem, isExpanded)
item = self._getTreeItem(tHandle) or self.invisibleRootItem()
self._recursiveSetExpanded(item, isExpanded)
return
def openContextOnSelected(self) -> bool:
"""Open the context menu on the current selected item."""
if items := self.selectedItems():
return self._openContextMenu(self.visualItemRect(items[0]).center())
return False
##
# Public Slots
##
def changedSince(self, checkTime: float) -> bool:
"""Check if the tree has changed since a given time."""
return self._timeChanged > checkTime
@pyqtSlot()
@pyqtSlot("QPoint")
def openContextMenu(self, clickPos: QPoint | None = None) -> None:
"""The user right clicked an element in the project tree, so we
open a context menu in-place.
"""
if clickPos is None and (items := self.selectedItems()):
clickPos = self.visualItemRect(items[0]).center()
if clickPos is not None:
tItem = None
tHandle = None
hasChild = False
sItem = self.itemAt(clickPos)
sItems = self.selectedItems()
if isinstance(sItem, QTreeWidgetItem):
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
tItem = SHARED.project.tree[tHandle]
hasChild = sItem.childCount() > 0
if tItem is None or tHandle is None:
logger.debug("No item found")
return
ctxMenu = _TreeContextMenu(self, tItem)
trashHandle = SHARED.project.tree.trashRoot
if trashHandle and tHandle == trashHandle:
ctxMenu.buildTrashMenu()
elif len(sItems) > 1:
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
ctxMenu.buildMultiSelectMenu(handles)
else:
ctxMenu.buildSingleSelectMenu(hasChild)
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
ctxMenu.deleteLater()
return
##
# Private Slots
@@ -1224,40 +1251,6 @@ class GuiProjectTree(QTreeWidget):
return
@pyqtSlot("QPoint")
def _openContextMenu(self, clickPos: QPoint) -> bool:
"""The user right clicked an element in the project tree, so we
open a context menu in-place.
"""
tItem = None
tHandle = None
hasChild = False
sItem = self.itemAt(clickPos)
sItems = self.selectedItems()
if isinstance(sItem, QTreeWidgetItem):
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
tItem = SHARED.project.tree[tHandle]
hasChild = sItem.childCount() > 0
if tItem is None or tHandle is None:
logger.debug("No item found")
return False
ctxMenu = _TreeContextMenu(self, tItem)
trashHandle = SHARED.project.tree.trashRoot
if trashHandle and tHandle == trashHandle:
ctxMenu.buildTrashMenu()
elif len(sItems) > 1:
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
ctxMenu.buildMultiSelectMenu(handles)
else:
ctxMenu.buildSingleSelectMenu(hasChild)
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
ctxMenu.deleteLater()
return True
@pyqtSlot()
def _doAutoScroll(self) -> None:
"""Scroll one item up or down based on direction value."""
@@ -1397,11 +1390,6 @@ class GuiProjectTree(QTreeWidget):
return
def _getItemWordCount(self, tHandle: str) -> int:
"""Return the word count of a given item handle."""
tItem = self._getTreeItem(tHandle)
return int(tItem.data(self.C_DATA, self.D_WORDS)) if tItem else 0
def _getTreeItem(self, tHandle: str | None) -> QTreeWidgetItem | None:
"""Return the QTreeWidgetItem of a given item handle."""
return self._treeMap.get(tHandle, None) if tHandle else None
@@ -1658,7 +1646,6 @@ class GuiProjectTree(QTreeWidget):
signals. A flush is only needed if an item is moved, created or
deleted.
"""
self._timeChanged = time()
SHARED.project.setProjectChanged(True)
if flush:
self.saveTreeOrder()
@@ -1785,7 +1772,7 @@ class _TreeContextMenu(QMenu):
# Edit Item Settings
action = self.addAction(self.tr("Rename"))
action.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle))
action.triggered.connect(qtLambda(self.projTree.renameTreeItem, self._handle))
if isFile:
self._itemHeader()
self._itemActive(False)
@@ -1821,13 +1808,15 @@ class _TreeContextMenu(QMenu):
def _docActions(self) -> None:
"""Add document actions."""
action = self.addAction(self.tr("Open Document"))
action.triggered.connect(
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.EDIT, "", True)
)
action.triggered.connect(qtLambda(
self.projView.openDocumentRequest.emit,
self._handle, nwDocMode.EDIT, "", True
))
action = self.addAction(self.tr("View Document"))
action.triggered.connect(
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.VIEW, "", False)
)
action.triggered.connect(qtLambda(
self.projView.openDocumentRequest.emit,
self._handle, nwDocMode.VIEW, "", False
))
return
def _itemCreation(self) -> None:
@@ -1846,7 +1835,7 @@ class _TreeContextMenu(QMenu):
if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
action = self.addAction(self.tr("Rename to Heading"))
action.triggered.connect(
lambda: self.projTree.renameTreeItem(self._handle, hItem.title)
qtLambda(self.projTree.renameTreeItem, self._handle, hItem.title)
)
return
@@ -1855,9 +1844,9 @@ class _TreeContextMenu(QMenu):
if multi:
mSub = self.addMenu(self.tr("Set Active to ..."))
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.projTree.trActive)
aOne.triggered.connect(lambda: self._iterItemActive(True))
aOne.triggered.connect(qtLambda(self._iterItemActive, True))
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive)
aTwo.triggered.connect(lambda: self._iterItemActive(False))
aTwo.triggered.connect(qtLambda(self._iterItemActive, False))
else:
action = self.addAction(self.tr("Toggle Active"))
action.triggered.connect(self._toggleItemActive)
@@ -1868,37 +1857,39 @@ class _TreeContextMenu(QMenu):
if self._item.isNovelLike():
menu = self.addMenu(self.tr("Set Status to ..."))
current = self._item.itemStatus
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()):
for key, entry in SHARED.project.data.itemStatus.iterItems():
name = entry.name
if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})"
action = menu.addAction(entry.icon, name)
if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
action.triggered.connect(qtLambda(self._iterSetItemStatus, key))
else:
action.triggered.connect(lambda n, key=key: self._changeItemStatus(key))
action.triggered.connect(qtLambda(self._changeItemStatus, key))
menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS)
)
action.triggered.connect(qtLambda(
self.projView.projectSettingsRequest.emit,
GuiProjectSettings.PAGE_STATUS
))
else:
menu = self.addMenu(self.tr("Set Importance to ..."))
current = self._item.itemImport
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()):
for key, entry in SHARED.project.data.itemImport.iterItems():
name = entry.name
if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})"
action = menu.addAction(entry.icon, name)
if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
action.triggered.connect(qtLambda(self._iterSetItemImport, key))
else:
action.triggered.connect(lambda n, key=key: self._changeItemImport(key))
action.triggered.connect(qtLambda(self._changeItemImport, key))
menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT)
)
action.triggered.connect(qtLambda(
self.projView.projectSettingsRequest.emit,
GuiProjectSettings.PAGE_IMPORT
))
return
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
@@ -1917,33 +1908,33 @@ class _TreeContextMenu(QMenu):
if isNoteFile and self._item.documentAllowed():
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
action.triggered.connect(lambda: self._changeItemLayout(loDoc))
action.triggered.connect(qtLambda(self._changeItemLayout, loDoc))
if isDocFile:
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
action.triggered.connect(lambda: self._changeItemLayout(loNote))
action.triggered.connect(qtLambda(self._changeItemLayout, loNote))
if isFolder and self._item.documentAllowed():
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
action.triggered.connect(lambda: self._covertFolderToFile(loDoc))
action.triggered.connect(qtLambda(self._covertFolderToFile, loDoc))
if isFolder:
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
action.triggered.connect(lambda: self._covertFolderToFile(loNote))
action.triggered.connect(qtLambda(self._covertFolderToFile, loNote))
if hasChild and isFile:
action = menu.addAction(self.tr("Merge Child Items into Self"))
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, False))
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, False))
action = menu.addAction(self.tr("Merge Child Items into New"))
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
if hasChild and isFolder:
action = menu.addAction(self.tr("Merge Documents in Folder"))
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
if isFile:
action = menu.addAction(self.tr("Split Document by Headings"))
action.triggered.connect(lambda: tree._splitDocument(tHandle))
action.triggered.connect(qtLambda(tree._splitDocument, tHandle))
return
@@ -1953,19 +1944,19 @@ class _TreeContextMenu(QMenu):
tHandle = self._handle
if hasChild:
action = self.addAction(self.tr("Expand All"))
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, True))
action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, True))
action = self.addAction(self.tr("Collapse All"))
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, False))
action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, False))
action = self.addAction(self.tr("Duplicate"))
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
action.triggered.connect(qtLambda(tree._duplicateFromHandle, tHandle))
if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
action = self.addAction(self.tr("Delete Permanently"))
action.triggered.connect(lambda: tree.permDeleteItem(tHandle))
action.triggered.connect(qtLambda(tree.permDeleteItem, tHandle))
else:
action = self.addAction(self.tr("Move to Trash"))
action.triggered.connect(lambda: tree.moveItemToTrash(tHandle))
action.triggered.connect(qtLambda(tree.moveItemToTrash, tHandle))
return
+7 -6
View File
@@ -32,6 +32,7 @@ from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
from novelwriter.enum import nwView
from novelwriter.extensions.eventfilters import StatusTipFilter
from novelwriter.extensions.modified import NIconToolButton
@@ -54,7 +55,7 @@ class GuiSideBar(QWidget):
self.mainGui = mainGui
iPx = int(1.2*SHARED.theme.baseButtonHeight)
iPx = int(1.25*SHARED.theme.baseButtonHeight)
iSz = QSize(iPx, iPx)
self.setContentsMargins(0, 0, 0, 0)
@@ -63,19 +64,19 @@ class GuiSideBar(QWidget):
# Buttons
self.tbProject = NIconToolButton(self, iSz)
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
self.tbProject.clicked.connect(lambda: self.requestViewChange.emit(nwView.PROJECT))
self.tbProject.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.PROJECT))
self.tbNovel = NIconToolButton(self, iSz)
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
self.tbNovel.clicked.connect(lambda: self.requestViewChange.emit(nwView.NOVEL))
self.tbNovel.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.NOVEL))
self.tbSearch = NIconToolButton(self, iSz)
self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Project Search")))
self.tbSearch.clicked.connect(lambda: self.requestViewChange.emit(nwView.SEARCH))
self.tbSearch.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH))
self.tbOutline = NIconToolButton(self, iSz)
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
self.tbOutline.clicked.connect(lambda: self.requestViewChange.emit(nwView.OUTLINE))
self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE))
self.tbBuild = NIconToolButton(self, iSz)
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
@@ -113,7 +114,7 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbSettings)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(CONFIG.pxInt(4))
self.outerBox.setSpacing(CONFIG.pxInt(6))
self.setLayout(self.outerBox)
self.updateTheme()
+19 -19
View File
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import describeFont
from novelwriter.common import describeFont, qtLambda
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.configlayout import (
@@ -329,16 +329,16 @@ class _FilterTab(NFixedPage):
self.includedButton = NIconToolButton(self, iSz)
self.includedButton.setToolTip(self.tr("Always included"))
self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED])
self.includedButton.clicked.connect(lambda: self._setSelectedMode(self.F_INCLUDED))
self.includedButton.clicked.connect(qtLambda(self._setSelectedMode, self.F_INCLUDED))
self.excludedButton = NIconToolButton(self, iSz)
self.excludedButton.setToolTip(self.tr("Always excluded"))
self.excludedButton.setIcon(self._statusFlags[self.F_EXCLUDED])
self.excludedButton.clicked.connect(lambda: self._setSelectedMode(self.F_EXCLUDED))
self.excludedButton.clicked.connect(qtLambda(self._setSelectedMode, self.F_EXCLUDED))
self.resetButton = NIconToolButton(self, iSz, "revert")
self.resetButton.setToolTip(self.tr("Reset to default"))
self.resetButton.clicked.connect(lambda: self._setSelectedMode(self.F_FILTERED))
self.resetButton.clicked.connect(qtLambda(self._setSelectedMode, self.F_FILTERED))
self.modeBox = QHBoxLayout()
self.modeBox.addWidget(QLabel(self.tr("Mark selection as"), self))
@@ -589,7 +589,7 @@ class _HeadingsTab(NScrollablePage):
self.fmtPart = QLineEdit("", self)
self.fmtPart.setReadOnly(True)
self.btnPart = NIconToolButton(self, iSz, "edit")
self.btnPart.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
self.btnPart.clicked.connect(qtLambda(self._editHeading, self.EDIT_TITLE))
self.hdePart = QLabel(trHide, self)
self.hdePart.setIndent(bSp)
self.swtPart = NSwitch(self, height=iPx)
@@ -605,7 +605,7 @@ class _HeadingsTab(NScrollablePage):
self.fmtChapter = QLineEdit("", self)
self.fmtChapter.setReadOnly(True)
self.btnChapter = NIconToolButton(self, iSz, "edit")
self.btnChapter.clicked.connect(lambda: self._editHeading(self.EDIT_CHAPTER))
self.btnChapter.clicked.connect(qtLambda(self._editHeading, self.EDIT_CHAPTER))
self.hdeChapter = QLabel(trHide, self)
self.hdeChapter.setIndent(bSp)
self.swtChapter = NSwitch(self, height=iPx)
@@ -621,7 +621,7 @@ class _HeadingsTab(NScrollablePage):
self.fmtUnnumbered = QLineEdit("", self)
self.fmtUnnumbered.setReadOnly(True)
self.btnUnnumbered = NIconToolButton(self, iSz, "edit")
self.btnUnnumbered.clicked.connect(lambda: self._editHeading(self.EDIT_UNNUM))
self.btnUnnumbered.clicked.connect(qtLambda(self._editHeading, self.EDIT_UNNUM))
self.hdeUnnumbered = QLabel(trHide, self)
self.hdeUnnumbered.setIndent(bSp)
self.swtUnnumbered = NSwitch(self, height=iPx)
@@ -637,7 +637,7 @@ class _HeadingsTab(NScrollablePage):
self.fmtScene = QLineEdit("", self)
self.fmtScene.setReadOnly(True)
self.btnScene = NIconToolButton(self, iSz, "edit")
self.btnScene.clicked.connect(lambda: self._editHeading(self.EDIT_SCENE))
self.btnScene.clicked.connect(qtLambda(self._editHeading, self.EDIT_SCENE))
self.hdeScene = QLabel(trHide, self)
self.hdeScene.setIndent(bSp)
self.swtScene = NSwitch(self, height=iPx)
@@ -653,7 +653,7 @@ class _HeadingsTab(NScrollablePage):
self.fmtAScene = QLineEdit("", self)
self.fmtAScene.setReadOnly(True)
self.btnAScene = NIconToolButton(self, iSz, "edit")
self.btnAScene.clicked.connect(lambda: self._editHeading(self.EDIT_HSCENE))
self.btnAScene.clicked.connect(qtLambda(self._editHeading, self.EDIT_HSCENE))
self.hdeAScene = QLabel(trHide, self)
self.hdeAScene.setIndent(bSp)
self.swtAScene = NSwitch(self, height=iPx)
@@ -669,7 +669,7 @@ class _HeadingsTab(NScrollablePage):
self.fmtSection = QLineEdit("", self)
self.fmtSection.setReadOnly(True)
self.btnSection = NIconToolButton(self, iSz, "edit")
self.btnSection.clicked.connect(lambda: self._editHeading(self.EDIT_SECTION))
self.btnSection.clicked.connect(qtLambda(self._editHeading, self.EDIT_SECTION))
self.hdeSection = QLabel(trHide, self)
self.hdeSection.setIndent(bSp)
self.swtSection = NSwitch(self, height=iPx)
@@ -702,15 +702,15 @@ class _HeadingsTab(NScrollablePage):
self.aInsCharPOV = self.menuInsert.addAction(self.tr("Point of View Character"))
self.aInsCharFocus = self.menuInsert.addAction(self.tr("Focus Character"))
self.aInsTitle.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.TITLE))
self.aInsChNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_NUM))
self.aInsChWord.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_WORD))
self.aInsChRomU.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROMU))
self.aInsChRomL.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROML))
self.aInsScNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_NUM))
self.aInsScAbs.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_ABS))
self.aInsCharPOV.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_POV))
self.aInsCharFocus.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_FOCUS))
self.aInsTitle.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.TITLE))
self.aInsChNum.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CH_NUM))
self.aInsChWord.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CH_WORD))
self.aInsChRomU.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CH_ROMU))
self.aInsChRomL.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CH_ROML))
self.aInsScNum.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.SC_NUM))
self.aInsScAbs.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.SC_ABS))
self.aInsCharPOV.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CHAR_POV))
self.aInsCharFocus.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CHAR_FOCUS))
self.btnInsert = QPushButton(self.tr("Insert"), self)
self.btnInsert.setMenu(self.menuInsert)
+3 -3
View File
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, checkIntTuple, formatTime, minmax
from novelwriter.common import checkInt, checkIntTuple, formatTime, minmax, qtLambda
from novelwriter.constants import nwConst
from novelwriter.error import formatException
from novelwriter.extensions.modified import NToolDialog
@@ -277,11 +277,11 @@ class GuiWritingStats(NToolDialog):
self.btnSave.setMenu(self.saveMenu)
self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self)
self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON))
self.saveJSON.triggered.connect(qtLambda(self._saveData, self.FMT_JSON))
self.saveMenu.addAction(self.saveJSON)
self.saveCSV = QAction(self.tr("CSV Data File (.csv)"), self)
self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV))
self.saveCSV.triggered.connect(qtLambda(self._saveData, self.FMT_CSV))
self.saveMenu.addAction(self.saveCSV)
# Assemble
+1 -1
View File
@@ -134,7 +134,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
assert docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu
assert nwGUI.projView.projTree.setSelectedHandle("88243afbe5ed8")
nwGUI.projView.projTree.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.ActionEvent.Trigger)
# Open context menu
+2 -1
View File
@@ -29,6 +29,7 @@ import pytest
from PyQt5.QtWidgets import QAction, QFileDialog, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwKeyWords
from novelwriter.enum import nwItemClass, nwOutline, nwView
from novelwriter.types import QtScrollAlwaysOff, QtScrollAsNeeded
@@ -261,7 +262,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, prjLipsum, fncPath, tstPat
assert outlineData.itemValue.text() == "Finished"
# Click POV Link
assert outlineData.povKeyValue.text() == "<a href='Bod'>Bod</a>"
assert outlineData.tagValues[nwKeyWords.POV_KEY][1].text() == "<a href='Bod'>Bod</a>"
outlineView._tagClicked("Bod")
assert nwGUI.docViewer.docHandle == "4c4f28287af27"
+16 -12
View File
@@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock
import pytest
@@ -107,7 +108,6 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
assert project.tree["0000000000013"].itemClass == nwItemClass.NOVEL # type: ignore
assert nwGUI.openDocument("0000000000013")
assert nwGUI.docEditor.getText() == "## New Chapter\n\n"
assert projTree._getItemWordCount("0000000000013") == 2
# Add a new scene next to the other new file
projView.setSelectedHandle("0000000000012")
@@ -117,7 +117,6 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
assert project.tree["0000000000014"].itemClass == nwItemClass.NOVEL # type: ignore
assert nwGUI.openDocument("0000000000014")
assert nwGUI.docEditor.getText() == "### New Scene\n\n"
assert projTree._getItemWordCount("0000000000014") == 2
# Add a new scene with the content copied from the previous
assert nwGUI.openDocument("0000000000014")
@@ -130,7 +129,6 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
assert project.tree["0000000000015"].itemClass == nwItemClass.NOVEL # type: ignore
assert nwGUI.openDocument("0000000000015")
assert nwGUI.docEditor.getText() == "### New Scene\n\nWith Stuff\n\n"
assert projTree._getItemWordCount("0000000000015") == 4
# Add a new file to the characters folder
projView.setSelectedHandle(C.hCharRoot)
@@ -140,7 +138,6 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
assert project.tree["0000000000016"].itemClass == nwItemClass.CHARACTER # type: ignore
assert nwGUI.openDocument("0000000000016")
assert nwGUI.docEditor.getText() == "# New Note\n\n"
assert projTree._getItemWordCount("0000000000016") == 2
# Make sure the sibling folder bug trap works
projView.setSelectedHandle("0000000000013")
@@ -1146,30 +1143,37 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
def itemPos(tHandle):
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
# Pop the menu
# Pop the menu in various positions and check for success
with monkeypatch.context() as mp:
mp.setattr(QMenu, "exec", lambda *a: None)
mockMenu = MagicMock()
mp.setattr(QMenu, "exec", mockMenu)
projTree.clearSelection()
# No item under menu
assert projTree._openContextMenu(projTree.viewport().rect().bottomRight()) is False
projTree.openContextMenu(projTree.viewport().rect().bottomRight())
assert mockMenu.call_count == 0
# Open Trash Menu
assert projTree._openContextMenu(itemPos(hTrashRoot)) is True
projTree.openContextMenu(itemPos(hTrashRoot))
assert mockMenu.call_count == 1
# Open Single Select Menu
assert projTree._openContextMenu(itemPos(C.hNovelRoot)) is True
projTree.openContextMenu(itemPos(C.hNovelRoot))
assert mockMenu.call_count == 2
# Open Multi-Select Menu
projTree._getTreeItem(hNovelNote).setSelected(True)
projTree._getTreeItem(hSubNote).setSelected(True)
assert projTree._openContextMenu(itemPos(hCharNote)) is True
projTree.openContextMenu(itemPos(hCharNote))
assert mockMenu.call_count == 3
# Check the keyboard shortcut handler as well
projTree.setSelectedHandle(C.hNovelRoot)
assert projTree.openContextOnSelected() is True
projTree.openContextMenu(None)
assert mockMenu.call_count == 4
projTree.clearSelection()
assert projTree.openContextOnSelected() is False
projTree.openContextMenu(None)
assert mockMenu.call_count == 4
# Menu Builders
# =============