Merge branch 'main' into release_1.0b2

This commit is contained in:
Veronica K. B. Olsen
2020-09-06 14:59:13 +02:00
4 changed files with 215 additions and 36 deletions
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()