Merge branch 'main' into release_1.0b2
This commit is contained in:
@@ -245,6 +245,7 @@ class nwUnicode:
|
|||||||
## Symbols
|
## Symbols
|
||||||
U_CHECK = "\u2714" # Heavy check mark
|
U_CHECK = "\u2714" # Heavy check mark
|
||||||
U_MULT = "\u2715" # Multiplication x
|
U_MULT = "\u2715" # Multiplication x
|
||||||
|
U_BULL = "\u2022" # List bullet
|
||||||
|
|
||||||
## Arrows
|
## Arrows
|
||||||
U_UTRI = "\u25b2" # Up-pointing triangle
|
U_UTRI = "\u25b2" # Up-pointing triangle
|
||||||
@@ -294,6 +295,7 @@ class nwUnicode:
|
|||||||
## Symbols
|
## Symbols
|
||||||
H_CHECK = "✔"
|
H_CHECK = "✔"
|
||||||
H_MULT = "✕"
|
H_MULT = "✕"
|
||||||
|
H_BULL = "•"
|
||||||
|
|
||||||
## Arrows
|
## Arrows
|
||||||
H_UTRI = "▲"
|
H_UTRI = "▲"
|
||||||
|
|||||||
+86
-31
@@ -732,41 +732,87 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""Triggered by right click to open the context menu. Also
|
"""Triggered by right click to open the context menu. Also
|
||||||
triggered by the Ctrl+. shortcut.
|
triggered by the Ctrl+. shortcut.
|
||||||
"""
|
"""
|
||||||
if not self.spellCheck:
|
userCursor = self.textCursor()
|
||||||
return
|
userSelection = userCursor.hasSelection()
|
||||||
|
|
||||||
theCursor = self.cursorForPosition(thePos)
|
mnuContext = QMenu()
|
||||||
theCursor.select(QTextCursor.WordUnderCursor)
|
|
||||||
|
|
||||||
theWord = theCursor.selectedText().strip().strip(self.nonWord)
|
# Cut, Copy and Paste
|
||||||
if theWord == "":
|
# ===================
|
||||||
return
|
|
||||||
|
|
||||||
logger.verbose("Looking up '%s' in the dictionary" % theWord)
|
if userSelection:
|
||||||
if self.theDict.checkWord(theWord):
|
mnuCut = QAction("Cut", mnuContext)
|
||||||
return
|
mnuCut.triggered.connect(lambda: self.docAction(nwDocAction.CUT))
|
||||||
|
mnuContext.addAction(mnuCut)
|
||||||
|
|
||||||
mnuSuggest = QMenu()
|
mnuCopy = QAction("Copy", mnuContext)
|
||||||
mnuHead = QAction("Spelling Suggestion(s)", mnuSuggest)
|
mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
|
||||||
mnuSuggest.addAction(mnuHead)
|
mnuContext.addAction(mnuCopy)
|
||||||
mnuSuggest.addSeparator()
|
|
||||||
theSuggest = self.theDict.suggestWords(theWord)
|
|
||||||
if len(theSuggest) > 0:
|
|
||||||
for aWord in theSuggest:
|
|
||||||
mnuWord = QAction(aWord, mnuSuggest)
|
|
||||||
mnuWord.triggered.connect(
|
|
||||||
lambda thePos, aWord=aWord : self._correctWord(theCursor, aWord)
|
|
||||||
)
|
|
||||||
mnuSuggest.addAction(mnuWord)
|
|
||||||
mnuSuggest.addSeparator()
|
|
||||||
mnuAdd = QAction("Add Word to Dictionary", mnuSuggest)
|
|
||||||
mnuAdd.triggered.connect(lambda thePos : self._addWord(theCursor))
|
|
||||||
mnuSuggest.addAction(mnuAdd)
|
|
||||||
else:
|
|
||||||
mnuHead = QAction("No Suggestions", mnuSuggest)
|
|
||||||
mnuSuggest.addAction(mnuHead)
|
|
||||||
|
|
||||||
mnuSuggest.exec_(self.viewport().mapToGlobal(thePos))
|
mnuPaste = QAction("Paste", mnuContext)
|
||||||
|
mnuPaste.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
|
||||||
|
mnuContext.addAction(mnuPaste)
|
||||||
|
|
||||||
|
mnuContext.addSeparator()
|
||||||
|
|
||||||
|
# Selections
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
mnuSelAll = QAction("Select All", mnuContext)
|
||||||
|
mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
|
||||||
|
mnuContext.addAction(mnuSelAll)
|
||||||
|
|
||||||
|
mnuSelWord = QAction("Select Word", mnuContext)
|
||||||
|
mnuSelWord.triggered.connect(
|
||||||
|
lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos)
|
||||||
|
)
|
||||||
|
mnuContext.addAction(mnuSelWord)
|
||||||
|
|
||||||
|
mnuSelPara = QAction("Select Paragraph", mnuContext)
|
||||||
|
mnuSelPara.triggered.connect(
|
||||||
|
lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos)
|
||||||
|
)
|
||||||
|
mnuContext.addAction(mnuSelPara)
|
||||||
|
|
||||||
|
# Spell Checking
|
||||||
|
# ==============
|
||||||
|
|
||||||
|
spellCheck = self.spellCheck
|
||||||
|
|
||||||
|
if spellCheck:
|
||||||
|
posCursor = self.cursorForPosition(thePos)
|
||||||
|
posCursor.select(QTextCursor.WordUnderCursor)
|
||||||
|
theWord = posCursor.selectedText().strip().strip(self.nonWord)
|
||||||
|
spellCheck &= theWord != ""
|
||||||
|
|
||||||
|
if spellCheck:
|
||||||
|
logger.verbose("Looking up '%s' in the dictionary" % theWord)
|
||||||
|
spellCheck &= not self.theDict.checkWord(theWord)
|
||||||
|
|
||||||
|
if spellCheck:
|
||||||
|
mnuContext.addSeparator()
|
||||||
|
mnuHead = QAction("Spelling Suggestion(s)", mnuContext)
|
||||||
|
mnuContext.addAction(mnuHead)
|
||||||
|
|
||||||
|
theSuggest = self.theDict.suggestWords(theWord)
|
||||||
|
if len(theSuggest) > 0:
|
||||||
|
for aWord in theSuggest:
|
||||||
|
mnuWord = QAction("%s %s" % (nwUnicode.U_ENDASH, aWord), mnuContext)
|
||||||
|
mnuWord.triggered.connect(
|
||||||
|
lambda thePos, aWord=aWord : self._correctWord(posCursor, aWord)
|
||||||
|
)
|
||||||
|
mnuContext.addAction(mnuWord)
|
||||||
|
mnuContext.addSeparator()
|
||||||
|
mnuAdd = QAction("Add Word to Dictionary", mnuContext)
|
||||||
|
mnuAdd.triggered.connect(lambda thePos : self._addWord(posCursor))
|
||||||
|
mnuContext.addAction(mnuAdd)
|
||||||
|
|
||||||
|
else:
|
||||||
|
mnuHead = QAction("No Suggestions", mnuContext)
|
||||||
|
mnuContext.addAction(mnuHead)
|
||||||
|
|
||||||
|
# Open the context menu
|
||||||
|
mnuContext.exec_(self.viewport().mapToGlobal(thePos))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1196,7 +1242,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _makeSelection(self, selMode):
|
def _makeSelection(self, selMode):
|
||||||
"""Wrapper function to select a word based on a selection mode.
|
"""Wrapper function to select text based on a selection mode.
|
||||||
"""
|
"""
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
theCursor.clearSelection()
|
theCursor.clearSelection()
|
||||||
@@ -1216,6 +1262,15 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _makePosSelection(self, selMode, thePos):
|
||||||
|
"""Wrapper function to select text based on selection mode, but
|
||||||
|
first move cursor to given position.
|
||||||
|
"""
|
||||||
|
theCursor = self.cursorForPosition(thePos)
|
||||||
|
self.setTextCursor(theCursor)
|
||||||
|
self._makeSelection(selMode)
|
||||||
|
return
|
||||||
|
|
||||||
def _beginSearch(self):
|
def _beginSearch(self):
|
||||||
"""Sets the selected text as the search text for the search bar.
|
"""Sets the selected text as the search text for the search bar.
|
||||||
"""
|
"""
|
||||||
|
|||||||
+73
-4
@@ -31,12 +31,13 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QUrl, QSize
|
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon
|
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton
|
QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
|
||||||
|
QAction, QMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.core import ToHtml
|
from nw.core import ToHtml
|
||||||
@@ -77,6 +78,10 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.anchorClicked.connect(self._linkClicked)
|
self.anchorClicked.connect(self._linkClicked)
|
||||||
self.setFocusPolicy(Qt.StrongFocus)
|
self.setFocusPolicy(Qt.StrongFocus)
|
||||||
|
|
||||||
|
# Context Menu
|
||||||
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.customContextMenuRequested.connect(self._openContextMenu)
|
||||||
|
|
||||||
logger.debug("GuiDocViewer initialisation complete")
|
logger.debug("GuiDocViewer initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -308,6 +313,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
# Slots
|
# Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@pyqtSlot("QUrl")
|
||||||
def _linkClicked(self, theURL):
|
def _linkClicked(self, theURL):
|
||||||
"""Slot for a link in the document being clicked.
|
"""Slot for a link in the document being clicked.
|
||||||
"""
|
"""
|
||||||
@@ -319,6 +325,49 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.loadFromTag(theBits[1])
|
self.loadFromTag(theBits[1])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot("QPoint")
|
||||||
|
def _openContextMenu(self, thePos):
|
||||||
|
"""Triggered by right click to open the context menu.
|
||||||
|
"""
|
||||||
|
userCursor = self.textCursor()
|
||||||
|
userSelection = userCursor.hasSelection()
|
||||||
|
|
||||||
|
mnuContext = QMenu()
|
||||||
|
|
||||||
|
# Cut, Copy and Paste
|
||||||
|
# ===================
|
||||||
|
|
||||||
|
if userSelection:
|
||||||
|
mnuCopy = QAction("Copy", mnuContext)
|
||||||
|
mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
|
||||||
|
mnuContext.addAction(mnuCopy)
|
||||||
|
|
||||||
|
mnuContext.addSeparator()
|
||||||
|
|
||||||
|
# Selections
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
mnuSelAll = QAction("Select All", mnuContext)
|
||||||
|
mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
|
||||||
|
mnuContext.addAction(mnuSelAll)
|
||||||
|
|
||||||
|
mnuSelWord = QAction("Select Word", mnuContext)
|
||||||
|
mnuSelWord.triggered.connect(
|
||||||
|
lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos)
|
||||||
|
)
|
||||||
|
mnuContext.addAction(mnuSelWord)
|
||||||
|
|
||||||
|
mnuSelPara = QAction("Select Paragraph", mnuContext)
|
||||||
|
mnuSelPara.triggered.connect(
|
||||||
|
lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos)
|
||||||
|
)
|
||||||
|
mnuContext.addAction(mnuSelPara)
|
||||||
|
|
||||||
|
# Open the context menu
|
||||||
|
mnuContext.exec_(self.viewport().mapToGlobal(thePos))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Events
|
# Events
|
||||||
##
|
##
|
||||||
@@ -335,13 +384,33 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _makeSelection(self, selMode):
|
def _makeSelection(self, selMode):
|
||||||
"""Wrapper function for making a selection based on a specific
|
"""Wrapper function to select text based on a selection mode.
|
||||||
selection mode.
|
|
||||||
"""
|
"""
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
theCursor.clearSelection()
|
theCursor.clearSelection()
|
||||||
theCursor.select(selMode)
|
theCursor.select(selMode)
|
||||||
|
|
||||||
|
if selMode == QTextCursor.BlockUnderCursor:
|
||||||
|
# This selection mode also selects the preceding oaragraph
|
||||||
|
# separator, which we want to avoid.
|
||||||
|
posS = theCursor.selectionStart()
|
||||||
|
posE = theCursor.selectionEnd()
|
||||||
|
selTxt = theCursor.selectedText()
|
||||||
|
if selTxt.startswith(nwUnicode.U_PSEP):
|
||||||
|
theCursor.setPosition(posS+1, QTextCursor.MoveAnchor)
|
||||||
|
theCursor.setPosition(posE, QTextCursor.KeepAnchor)
|
||||||
|
|
||||||
self.setTextCursor(theCursor)
|
self.setTextCursor(theCursor)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _makePosSelection(self, selMode, thePos):
|
||||||
|
"""Wrapper function to select text based on selection mode, but
|
||||||
|
first move cursor to given position.
|
||||||
|
"""
|
||||||
|
theCursor = self.cursorForPosition(thePos)
|
||||||
|
self.setTextCursor(theCursor)
|
||||||
|
self._makeSelection(selMode)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _makeStyleSheet(self):
|
def _makeStyleSheet(self):
|
||||||
|
|||||||
+54
-1
@@ -10,6 +10,7 @@ from nwtools import cmpFiles
|
|||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
from PyQt5.QtCore import Qt, QPoint
|
from PyQt5.QtCore import Qt, QPoint
|
||||||
|
from PyQt5.QtGui import QTextCursor
|
||||||
from PyQt5.QtWidgets import QAction, QDialogButtonBox, QTreeWidgetItem
|
from PyQt5.QtWidgets import QAction, QDialogButtonBox, QTreeWidgetItem
|
||||||
|
|
||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
@@ -397,7 +398,7 @@ def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
testFile = path.join(nwTempGUI, "2_nwProject.nwx")
|
testFile = path.join(nwTempGUI, "2_nwProject.nwx")
|
||||||
refFile = path.join(nwRef, "gui", "2_nwProject.nwx")
|
refFile = path.join(nwRef, "gui", "2_nwProject.nwx")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
assert cmpFiles(testFile, refFile, [2, 8, 9, 10])
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
@@ -1110,6 +1111,58 @@ def testDocAction(qtbot, nwLipsum, nwTemp):
|
|||||||
assert nwGUI.docEditor.getText()[27:74] == cleanText
|
assert nwGUI.docEditor.getText()[27:74] == cleanText
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Editor Context Menu
|
||||||
|
theCursor = nwGUI.docEditor.textCursor()
|
||||||
|
theCursor.setPosition(100)
|
||||||
|
nwGUI.docEditor.setTextCursor(theCursor)
|
||||||
|
theRect = nwGUI.docEditor.cursorRect()
|
||||||
|
|
||||||
|
nwGUI.docEditor._openContextMenu(theRect.bottomRight())
|
||||||
|
qtbot.mouseClick(nwGUI.docEditor, Qt.LeftButton, pos=theRect.topLeft())
|
||||||
|
|
||||||
|
nwGUI.docEditor._makePosSelection(QTextCursor.WordUnderCursor, theRect.center())
|
||||||
|
theCursor = nwGUI.docEditor.textCursor()
|
||||||
|
assert theCursor.selectedText() == "imperdiet"
|
||||||
|
|
||||||
|
nwGUI.docEditor._makePosSelection(QTextCursor.BlockUnderCursor, theRect.center())
|
||||||
|
theCursor = nwGUI.docEditor.textCursor()
|
||||||
|
assert theCursor.selectedText() == (
|
||||||
|
"Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
|
||||||
|
"imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
|
||||||
|
"placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
|
||||||
|
"Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
|
||||||
|
"Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
|
||||||
|
"rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
|
||||||
|
"nunc lacus, imperdiet nec posuere ac, interdum non lectus."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Viewer Context Menu
|
||||||
|
assert nwGUI.viewDocument("4c4f28287af27")
|
||||||
|
|
||||||
|
theCursor = nwGUI.docViewer.textCursor()
|
||||||
|
theCursor.setPosition(100)
|
||||||
|
nwGUI.docViewer.setTextCursor(theCursor)
|
||||||
|
theRect = nwGUI.docViewer.cursorRect()
|
||||||
|
|
||||||
|
nwGUI.docViewer._openContextMenu(theRect.bottomRight())
|
||||||
|
qtbot.mouseClick(nwGUI.docViewer, Qt.LeftButton, pos=theRect.topLeft())
|
||||||
|
|
||||||
|
nwGUI.docViewer._makePosSelection(QTextCursor.WordUnderCursor, theRect.center())
|
||||||
|
theCursor = nwGUI.docViewer.textCursor()
|
||||||
|
assert theCursor.selectedText() == "imperdiet"
|
||||||
|
|
||||||
|
nwGUI.docEditor._makePosSelection(QTextCursor.BlockUnderCursor, theRect.center())
|
||||||
|
theCursor = nwGUI.docEditor.textCursor()
|
||||||
|
assert theCursor.selectedText() == (
|
||||||
|
"Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
|
||||||
|
"imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
|
||||||
|
"placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
|
||||||
|
"Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
|
||||||
|
"Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
|
||||||
|
"rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
|
||||||
|
"nunc lacus, imperdiet nec posuere ac, interdum non lectus."
|
||||||
|
)
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user