Merge branch 'dev' into new_ref_panel
This commit is contained in:
@@ -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 2018–2023, 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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user