Merge branch 'main' into features/footnotes

This commit is contained in:
Veronica Berglyd Olsen
2024-04-14 22:17:23 +02:00
120 changed files with 5019 additions and 4672 deletions
+22 -20
View File
@@ -381,7 +381,7 @@ class GuiDocEditor(QPlainTextEdit):
return
def loadText(self, tHandle: str, tLine=None) -> bool:
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
"""Load text from a document into the editor. If we have an I/O
error, we must handle this and clear the editor so that we don't
risk overwriting the file if it exists. This can for instance
@@ -497,6 +497,7 @@ class GuiDocEditor(QPlainTextEdit):
return False
self.setDocumentChanged(False)
self.docTextChanged.emit(self._docHandle, self._lastEdit)
oldHeader = self._nwItem.mainHeading
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
@@ -1080,7 +1081,7 @@ class GuiDocEditor(QPlainTextEdit):
return
@pyqtSlot()
def _cursorMoved(self):
def _cursorMoved(self) -> None:
"""Triggered when the cursor moved in the editor."""
self.docFooter.updateLineCount(self.textCursor())
return
@@ -1470,7 +1471,7 @@ class GuiDocEditor(QPlainTextEdit):
# Internal Functions : Text Manipulation
##
def _toggleFormat(self, fLen: int, fChar: str) -> bool:
def _toggleFormat(self, fLen: int, fChar: str) -> None:
"""Toggle the formatting of a specific type for a piece of text.
If more than one block is selected, the formatting is applied to
the first block.
@@ -1488,12 +1489,12 @@ class GuiDocEditor(QPlainTextEdit):
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
if self._qDocument.characterAt(posO - 1) == fChar:
if posS == posE and self._qDocument.characterAt(posO - 1) == fChar:
logger.warning("Format repetition, cancelling action")
cursor.clearSelection()
cursor.setPosition(posO)
self.setTextCursor(cursor)
return False
return
blockS = self._qDocument.findBlock(posS)
blockE = self._qDocument.findBlock(posE)
@@ -1519,7 +1520,6 @@ class GuiDocEditor(QPlainTextEdit):
break
if fLen == min(numA, numB):
cursor.clearSelection()
cursor.beginEditBlock()
cursor.setPosition(posS)
for i in range(fLen):
@@ -1528,17 +1528,19 @@ class GuiDocEditor(QPlainTextEdit):
for i in range(fLen):
cursor.deletePreviousChar()
cursor.endEditBlock()
cursor.clearSelection()
cursor.setPosition(posO - fLen)
self.setTextCursor(cursor)
if select != _SelectAction.KEEP_SELECTION:
cursor.clearSelection()
cursor.setPosition(posO - fLen)
self.setTextCursor(cursor)
else:
self._wrapSelection(fChar*fLen, pos=posO, select=select)
return True
return
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
select: _SelectAction = _SelectAction.NO_DECISION) -> bool:
select: _SelectAction = _SelectAction.NO_DECISION) -> None:
"""Wrap the selected text in whatever is in tBefore and tAfter.
If there is no selection, the autoSelect setting decides the
action. AutoSelect will select the word under the cursor before
@@ -1578,21 +1580,21 @@ class GuiDocEditor(QPlainTextEdit):
if select == _SelectAction.MOVE_AFTER:
cursor.setPosition(posE + len(before + after))
elif select == _SelectAction.KEEP_SELECTION:
cursor.setPosition(posE + len(before), QtMoveAnchor)
cursor.setPosition(posS + len(before), QtKeepAnchor)
cursor.setPosition(posS + len(before), QtMoveAnchor)
cursor.setPosition(posE + len(before), QtKeepAnchor)
elif select == _SelectAction.KEEP_POSITION:
cursor.setPosition(posO + len(before))
self.setTextCursor(cursor)
return True
return
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> bool:
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
"""Replace all straight quotes in the selected text."""
cursor = self.textCursor()
if not cursor.hasSelection():
SHARED.error(self.tr("Please select some text before calling replace quotes."))
return False
return
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
@@ -1632,7 +1634,7 @@ class GuiDocEditor(QPlainTextEdit):
self._allowAutoReplace(True)
return True
return
def _processBlockFormat(
self, action: nwDocAction, text: str, toggle: bool = True
@@ -2185,7 +2187,7 @@ class MetaCompleter(QMenu):
# Internal Functions
##
def _emitComplete(self, pos: int, length: int, value: str):
def _emitComplete(self, pos: int, length: int, value: str) -> None:
"""Emit the signal to indicate a selection has been made."""
self.complete.emit(pos, length, value)
return
@@ -2966,7 +2968,7 @@ class GuiDocEditHeader(QWidget):
# Events
##
def mousePressEvent(self, event: QMouseEvent):
def mousePressEvent(self, event: QMouseEvent) -> None:
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
@@ -3134,7 +3136,7 @@ class GuiDocEditFooter(QWidget):
sText = ""
else:
iPx = round(0.9*SHARED.theme.baseIconHeight)
status, icon = self._tItem.getImportStatus(incIcon=True)
status, icon = self._tItem.getImportStatus()
sIcon = icon.pixmap(iPx, iPx)
sText = f"{status} / {self._tItem.describeMe()}"
+1 -1
View File
@@ -328,7 +328,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(0, 3, self._hStyles["head2h"])
self.setFormat(3, len(text), self._hStyles["header2"])
elif text.startswith("###! "): # Hard Scene
elif text.startswith("###! "): # Alternative Scene
self.setFormat(0, 4, self._hStyles["head3h"])
self.setFormat(4, len(text), self._hStyles["header3"])
+1 -1
View File
@@ -450,7 +450,7 @@ class _ViewPanelKeyWords(QTreeWidget):
nwItem.itemType, nwItem.itemClass,
nwItem.itemLayout, nwItem.mainHeading
)
impLabel, impIcon = nwItem.getImportStatus(incIcon=True)
impLabel, impIcon = nwItem.getImportStatus()
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
+1 -1
View File
@@ -253,7 +253,7 @@ class GuiItemDetails(QWidget):
# Status
# ======
status, icon = nwItem.getImportStatus(incIcon=True)
status, icon = nwItem.getImportStatus()
self.statusIcon.setPixmap(icon.pixmap(iPx, iPx))
self.statusData.setText(status)
+2 -2
View File
@@ -742,8 +742,8 @@ class GuiMainMenu(QMenuBar):
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN)
)
# Format > Hard Scene
self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Hard Scene"))
# Format > Alternative Scene
self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Alternative Scene"))
self.aFmtHardSc.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_HSC)
)
+8 -5
View File
@@ -35,8 +35,8 @@ from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget
QInputDialog, QMenu, QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget
)
from novelwriter import CONFIG, SHARED
@@ -47,7 +47,10 @@ from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
from novelwriter.types import QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtUserRole
from novelwriter.types import (
QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
QtUserRole
)
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
@@ -215,7 +218,7 @@ class GuiNovelToolBar(QWidget):
self.novelValue.setFont(selFont)
self.novelValue.setListFormat(self.tr("Outline of {0}"))
self.novelValue.setMinimumWidth(CONFIG.pxInt(150))
self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
self.tbNovel = NIconToolButton(self, iSz)
@@ -344,7 +347,7 @@ class GuiNovelToolBar(QWidget):
# Internal Functions
##
def _addLastColAction(self, colType, actionLabel) -> None:
def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None:
"""Add a column selection entry to the last column menu."""
aLast = self.mLastCol.addAction(actionLabel)
aLast.setCheckable(True)
+10 -11
View File
@@ -36,20 +36,19 @@ from enum import Enum
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP
from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar,
QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.enum import (
nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
)
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
from novelwriter.error import logException
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.types import (
QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, QtUserRole
QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration,
QtSizeExpanding, QtUserRole
)
@@ -190,7 +189,7 @@ class GuiOutlineView(QWidget):
return
@pyqtSlot(str)
def _rootItemChanged(self, tHandle) -> None:
def _rootItemChanged(self, tHandle: str) -> None:
"""Handle root novel changed or needs to be refreshed."""
self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
return
@@ -217,7 +216,7 @@ class GuiOutlineToolBar(QToolBar):
self.setContentsMargins(0, 0, 0, 0)
stretch = QWidget(self)
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
# Novel Selector
self.novelLabel = QLabel(self.tr("Outline of"), self)
@@ -428,7 +427,7 @@ class GuiOutlineTree(QTreeWidget):
##
@property
def hiddenColumns(self):
def hiddenColumns(self) -> dict[nwOutline, bool]:
return self._colHidden
##
@@ -586,7 +585,7 @@ class GuiOutlineTree(QTreeWidget):
# Internal Functions
##
def _loadHeaderState(self):
def _loadHeaderState(self) -> None:
"""Load the state of the main tree header, that is, column order
and column width.
"""
@@ -1048,7 +1047,7 @@ class GuiOutlineDetails(QScrollArea):
self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
self.titleValue.setText(novIdx.title)
itemStatus, _ = nwItem.getImportStatus(incIcon=False)
itemStatus, _ = nwItem.getImportStatus()
self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(itemStatus)
+15 -12
View File
@@ -38,8 +38,8 @@ from PyQt5.QtGui import (
)
from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget
QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget
)
from novelwriter import CONFIG, SHARED
@@ -54,7 +54,10 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
from novelwriter.types import (
QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
QtUserRole,
)
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
@@ -274,7 +277,7 @@ class GuiProjectToolBar(QWidget):
self.viewLabel = QLabel(self.tr("Project Content"), self)
self.viewLabel.setFont(SHARED.theme.guiFontB)
self.viewLabel.setContentsMargins(0, 0, 0, 0)
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
# Quick Links
self.mQuick = QMenu(self)
@@ -453,7 +456,7 @@ class GuiProjectToolBar(QWidget):
def _buildRootMenu(self) -> None:
"""Build the rood folder menu."""
def addClass(itemClass):
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))
@@ -1033,7 +1036,7 @@ class GuiProjectTree(QTreeWidget):
if trItem is None or nwItem is None:
return
itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = nwItem.mainHeading
itemIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
@@ -1855,11 +1858,11 @@ 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.items()):
name = entry["name"]
for n, (key, entry) in enumerate(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)
action = menu.addAction(entry.icon, name)
if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
else:
@@ -1872,11 +1875,11 @@ class _TreeContextMenu(QMenu):
else:
menu = self.addMenu(self.tr("Set Importance to ..."))
current = self._item.itemImport
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
name = entry["name"]
for n, (key, entry) in enumerate(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)
action = menu.addAction(entry.icon, name)
if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
else:
+15 -5
View File
@@ -74,9 +74,9 @@ class GuiTheme:
self.isLightTheme = True
# GUI
self.statNone = QColor(120, 120, 120)
self.statUnsaved = QColor(200, 15, 39)
self.statSaved = QColor(2, 133, 37)
self.statNone = QColor(0, 0, 0)
self.statUnsaved = QColor(0, 0, 0)
self.statSaved = QColor(0, 0, 0)
self.helpText = QColor(0, 0, 0)
# Loaded Syntax Settings
@@ -227,6 +227,10 @@ class GuiTheme:
logException()
return False
# Reset Palette
self._guiPalette = QApplication.style().standardPalette()
self._resetGuiColors()
# Main
sec = "Main"
if parser.has_section(sec):
@@ -256,8 +260,6 @@ class GuiTheme:
self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText)
self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
else:
self._guiPalette = QApplication.style().standardPalette()
# GUI
sec = "GUI"
@@ -393,6 +395,14 @@ class GuiTheme:
# Internal Functions
##
def _resetGuiColors(self) -> None:
"""Reset GUI colours to default values."""
self.statNone = QColor(120, 120, 120)
self.statUnsaved = QColor(200, 15, 39)
self.statSaved = QColor(2, 133, 37)
self.helpText = QColor(0, 0, 0)
return
def _setGuiFont(self) -> None:
"""Update the GUI's font style from settings."""
font = QFont()