Merge branch 'dev' into new_ref_panel

This commit is contained in:
Veronica Berglyd Olsen
2023-11-14 11:06:04 +01:00
84 changed files with 673 additions and 1761 deletions
+99 -56
View File
@@ -10,6 +10,8 @@ Created: 2020-04-25 [0.4.5] GuiDocEditHeader
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
Created: 2020-06-27 [0.10] GuiDocEditFooter
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
Created: 2023-11-06 [2.2b1] MetaCompleter
Created: 2023-11-07 [2.2b1] GuiDocToolBar
This file is a part of novelWriter
Copyright 20182023, Veronica Berglyd Olsen
@@ -67,6 +69,16 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
class _SelectAction(Enum):
NO_DECISION = 0
KEEP_SELECTION = 1
KEEP_POSITION = 2
MOVE_AFTER = 3
# END Class _SelectAction
class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor"""
@@ -152,7 +164,6 @@ class GuiDocEditor(QPlainTextEdit):
self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True)
self.setFrameStyle(QFrame.NoFrame)
self.setCenterOnScroll(True)
# Custom Shortcuts
self.keyContext = QShortcut(self)
@@ -330,7 +341,8 @@ class GuiDocEditor(QPlainTextEdit):
self._qDocument.setDefaultTextOption(options)
# Scroll bars
# Scrolling
self.setCenterOnScroll(CONFIG.scrollPastEnd)
if CONFIG.hideVScroll:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
else:
@@ -996,7 +1008,9 @@ class GuiDocEditor(QPlainTextEdit):
return
text = block.text()
if text.startswith("@"):
if text.startswith("@") and added + removed == 1:
# Only run on single keypresses, otherwise it will trigger
# at unwanted times when other changes are made to the document
cursor = self.textCursor()
bPos = cursor.positionInBlock()
if bPos > 0:
@@ -1004,8 +1018,10 @@ class GuiDocEditor(QPlainTextEdit):
point = self.cursorRect().bottomRight()
self._completer.move(self.viewport().mapToGlobal(point))
self._completer.setVisible(show)
else:
self._completer.setVisible(False)
elif self._doReplace and added == 1:
if self._doReplace and added == 1:
self._docAutoReplace(text)
return
@@ -1410,17 +1426,28 @@ class GuiDocEditor(QPlainTextEdit):
If more than one block is selected, the formatting is applied to
the first block.
"""
cursor = self._autoSelect()
if not cursor.hasSelection():
logger.warning("No selection made, nothing to do")
return False
cursor = self.textCursor()
posO = cursor.position()
if cursor.hasSelection():
select = _SelectAction.KEEP_SELECTION
else:
cursor = self._autoSelect()
if cursor.hasSelection() and posO == cursor.selectionEnd():
select = _SelectAction.MOVE_AFTER
else:
select = _SelectAction.KEEP_POSITION
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
if self._qDocument.characterAt(posO - 1) == fChar:
logger.warning("Format repetition, cancelling action")
cursor.clearSelection()
cursor.setPosition(posO)
self.setTextCursor(cursor)
return False
blockS = self._qDocument.findBlock(posS)
blockE = self._qDocument.findBlock(posE)
if blockS != blockE:
posE = blockS.position() + blockS.length() - 1
cursor.clearSelection()
@@ -1443,34 +1470,26 @@ class GuiDocEditor(QPlainTextEdit):
break
if fLen == min(numA, numB):
self._clearSurrounding(cursor, fLen)
cursor.clearSelection()
cursor.beginEditBlock()
cursor.setPosition(posS)
for i in range(fLen):
cursor.deletePreviousChar()
cursor.setPosition(posE)
for i in range(fLen):
cursor.deletePreviousChar()
cursor.endEditBlock()
cursor.clearSelection()
cursor.setPosition(posO - fLen)
self.setTextCursor(cursor)
else:
self._wrapSelection(fChar*fLen)
self._wrapSelection(fChar*fLen, pos=posO, select=select)
return True
def _clearSurrounding(self, cursor: QTextCursor, nChars: int) -> bool:
"""Clear n characters before and after the cursor."""
if not cursor.hasSelection():
logger.warning("No selection made, nothing to do")
return False
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
cursor.clearSelection()
cursor.beginEditBlock()
cursor.setPosition(posS)
for i in range(nChars):
cursor.deletePreviousChar()
cursor.setPosition(posE)
for i in range(nChars):
cursor.deletePreviousChar()
cursor.endEditBlock()
cursor.clearSelection()
return True
def _wrapSelection(self, before: str, after: str | None = None) -> bool:
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
select: _SelectAction = _SelectAction.NO_DECISION) -> bool:
"""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
@@ -1479,10 +1498,17 @@ class GuiDocEditor(QPlainTextEdit):
if after is None:
after = before
cursor = self._autoSelect()
if not cursor.hasSelection():
logger.warning("No selection made, nothing to do")
return False
cursor = self.textCursor()
posO = pos if isinstance(pos, int) else cursor.position()
if select == _SelectAction.NO_DECISION:
if cursor.hasSelection():
select = _SelectAction.KEEP_SELECTION
else:
cursor = self._autoSelect()
if cursor.hasSelection() and posO == cursor.selectionEnd():
select = _SelectAction.MOVE_AFTER
else:
select = _SelectAction.KEEP_POSITION
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
@@ -1500,8 +1526,14 @@ class GuiDocEditor(QPlainTextEdit):
cursor.insertText(before)
cursor.endEditBlock()
cursor.setPosition(posE + len(before), QTextCursor.MoveAnchor)
cursor.setPosition(posS + len(before), QTextCursor.KeepAnchor)
if select == _SelectAction.MOVE_AFTER:
cursor.setPosition(posE + len(before + after))
elif select == _SelectAction.KEEP_SELECTION:
cursor.setPosition(posE + len(before), QTextCursor.MoveMode.MoveAnchor)
cursor.setPosition(posS + len(before), QTextCursor.MoveMode.KeepAnchor)
elif select == _SelectAction.KEEP_POSITION:
cursor.setPosition(posO + len(before))
self.setTextCursor(cursor)
return True
@@ -1908,27 +1940,38 @@ class GuiDocEditor(QPlainTextEdit):
def _autoSelect(self) -> QTextCursor:
"""Return a cursor which may or may not have a selection based
on user settings and document action.
on user settings and document action. The selection will be the
word closest to the cursor consisting of alphanumerical unicode
characters.
"""
cursor = self.textCursor()
if CONFIG.autoSelect and not cursor.hasSelection():
cursor.select(QTextCursor.WordUnderCursor)
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
cPos = cursor.position()
bPos = cursor.block().position()
bLen = cursor.block().length()
# Underscore counts as a part of the word, so check that the
# selection isn't wrapped in italics markers.
reSelect = False
if self._qDocument.characterAt(posS) == "_":
posS += 1
reSelect = True
if self._qDocument.characterAt(posE) == "_":
posE -= 1
reSelect = True
if reSelect:
cursor.clearSelection()
cursor.setPosition(posS, QTextCursor.MoveAnchor)
cursor.setPosition(posE-1, QTextCursor.KeepAnchor)
# Scan backwards
sPos = cPos
for i in range(cPos - bPos):
sPos = cPos - i - 1
if not self._qDocument.characterAt(sPos).isalnum():
sPos += 1
break
# Scan forwards
ePos = cPos
for i in range(bPos + bLen - cPos):
ePos = cPos + i
if not self._qDocument.characterAt(ePos).isalnum():
break
if ePos - sPos <= 0:
# No selection possible
return cursor
cursor.clearSelection()
cursor.setPosition(sPos, QTextCursor.MoveAnchor)
cursor.setPosition(ePos, QTextCursor.KeepAnchor)
self.setTextCursor(cursor)
+36 -20
View File
@@ -54,6 +54,7 @@ class GuiMainMenu(QMenuBar):
requestDocInsert = pyqtSignal(nwDocInsert)
requestDocInsertText = pyqtSignal(str)
requestDocKeyWordInsert = pyqtSignal(str)
requestFocusChange = pyqtSignal(nwWidget)
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -173,7 +174,7 @@ class GuiMainMenu(QMenuBar):
# Project > Delete
self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item"))
self.aDeleteItem.setShortcut("Ctrl+Shift+Del")
self.aDeleteItem.setShortcuts(["Ctrl+Del", "Ctrl+Shift+Del"]) # Latter is deprecated
self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None))
# Project > Empty Trash
@@ -246,12 +247,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
self.aEditUndo.setShortcut("Ctrl+Z")
self.aEditUndo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.UNDO))
self.aEditUndo.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.UNDO)
)
# Edit > Redo
self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
self.aEditRedo.setShortcut("Ctrl+Y")
self.aEditRedo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.REDO))
self.aEditRedo.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.REDO)
)
# Edit > Separator
self.editMenu.addSeparator()
@@ -259,17 +264,23 @@ class GuiMainMenu(QMenuBar):
# Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
self.aEditCut.setShortcut("Ctrl+X")
self.aEditCut.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.CUT))
self.aEditCut.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.CUT)
)
# Edit > Copy
self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
self.aEditCopy.setShortcut("Ctrl+C")
self.aEditCopy.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.COPY))
self.aEditCopy.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.COPY)
)
# Edit > Paste
self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
self.aEditPaste.setShortcut("Ctrl+V")
self.aEditPaste.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.PASTE))
self.aEditPaste.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.PASTE)
)
# Edit > Separator
self.editMenu.addSeparator()
@@ -277,12 +288,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
self.aSelectAll.setShortcut("Ctrl+A")
self.aSelectAll.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL))
self.aSelectAll.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL)
)
# Edit > Select Paragraph
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A")
self.aSelectPar.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA))
self.aSelectPar.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA)
)
return
@@ -293,23 +308,24 @@ class GuiMainMenu(QMenuBar):
# View > TreeView
self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree"))
self.aFocusTree.setShortcut("Ctrl+Alt+1" if CONFIG.osWindows else "Alt+1")
self.aFocusTree.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE))
self.aFocusTree.setShortcut("Ctrl+T")
self.aFocusTree.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.TREE)
)
# View > Document Pane 1
# View > Document Editor
self.aFocusEditor = self.viewMenu.addAction(self.tr("Go to Document Editor"))
self.aFocusEditor.setShortcut("Ctrl+Alt+2" if CONFIG.osWindows else "Alt+2")
self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.EDITOR))
# View > Document Pane 2
self.aFocusView = self.viewMenu.addAction(self.tr("Go to Document Viewer"))
self.aFocusView.setShortcut("Ctrl+Alt+3" if CONFIG.osWindows else "Alt+3")
self.aFocusView.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.VIEWER))
self.aFocusEditor.setShortcut("Ctrl+E")
self.aFocusEditor.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.EDITOR)
)
# View > Outline
self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to Outline"))
self.aFocusOutline.setShortcut("Ctrl+Alt+4" if CONFIG.osWindows else "Alt+4")
self.aFocusOutline.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE))
self.aFocusOutline.setShortcut("Ctrl+Shift+T")
self.aFocusOutline.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.OUTLINE)
)
# View > Separator
self.viewMenu.addSeparator()
+1 -1
View File
@@ -189,7 +189,7 @@ class GuiProjectView(QWidget):
self.projTree.buildTree()
return
def setFocus(self) -> None:
def setTreeFocus(self) -> None:
"""Forward the set focus call to the tree widget."""
self.projTree.setFocus()
return
+8 -8
View File
@@ -57,32 +57,32 @@ class GuiSideBar(QWidget):
# Buttons
self.tbProject = QToolButton(self)
self.tbProject.setToolTip(self.tr("Project Tree View"))
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
self.tbProject.setIconSize(iconSize)
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
self.tbNovel = QToolButton(self)
self.tbNovel.setToolTip(self.tr("Novel Tree View"))
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
self.tbNovel.setIconSize(iconSize)
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
self.tbOutline = QToolButton(self)
self.tbOutline.setToolTip(self.tr("Novel Outline View"))
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
self.tbOutline.setIconSize(iconSize)
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
self.tbBuild = QToolButton(self)
self.tbBuild.setToolTip(self.tr("Build Manuscript"))
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
self.tbBuild.setIconSize(iconSize)
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
self.tbDetails = QToolButton(self)
self.tbDetails.setToolTip(self.tr("Project Details"))
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Project Details")))
self.tbDetails.setIconSize(iconSize)
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
self.tbStats = QToolButton(self)
self.tbStats.setToolTip(self.tr("Writing Statistics"))
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
self.tbStats.setIconSize(iconSize)
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
@@ -111,7 +111,7 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbSettings)
self.outerBox.setContentsMargins(0, 0, CONFIG.pxInt(2), 0)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(CONFIG.pxInt(4))
self.setLayout(self.outerBox)
@@ -131,7 +131,7 @@ class GuiSideBar(QWidget):
buttonStyle = (
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(4), fadeCol.red(), fadeCol.green(), fadeCol.blue())
).format(CONFIG.pxInt(6), fadeCol.red(), fadeCol.green(), fadeCol.blue())
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))