Merge branch 'main' into features/footnotes
This commit is contained in:
@@ -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()}"
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user