Adjust windows for IME conversion suggestions to not cover text (#2518)
This commit is contained in:
@@ -39,13 +39,14 @@ from enum import Enum, IntFlag
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
|
QObject, QPoint, QRect, QRegularExpression, QRunnable, Qt, QTimer,
|
||||||
pyqtSlot
|
QVariant, pyqtSignal, pyqtSlot
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
|
QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent,
|
||||||
QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut,
|
QInputMethodEvent, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap,
|
||||||
QTextBlock, QTextCursor, QTextDocument, QTextOption
|
QResizeEvent, QShortcut, QTextBlock, QTextCursor, QTextDocument,
|
||||||
|
QTextOption
|
||||||
)
|
)
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
||||||
@@ -74,8 +75,9 @@ from novelwriter.text.counting import standardCounter
|
|||||||
from novelwriter.tools.lipsum import GuiLipsum
|
from novelwriter.tools.lipsum import GuiLipsum
|
||||||
from novelwriter.types import (
|
from novelwriter.types import (
|
||||||
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
|
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
|
||||||
QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft,
|
QtAlignRight, QtImCursorRectangle, QtKeepAnchor, QtModCtrl, QtModNone,
|
||||||
QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded
|
QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveLeft, QtMoveRight,
|
||||||
|
QtScrollAlwaysOff, QtScrollAsNeeded
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -914,7 +916,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Document Events and Maintenance
|
# Events and Overloads
|
||||||
##
|
##
|
||||||
|
|
||||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||||
@@ -1017,6 +1019,26 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def inputMethodEvent(self, event: QInputMethodEvent) -> None:
|
||||||
|
"""Handle text being input from CJK input methods."""
|
||||||
|
super().inputMethodEvent(event)
|
||||||
|
if event.commitString():
|
||||||
|
# See issues #2267 and #2517
|
||||||
|
self.ensureCursorVisible()
|
||||||
|
self._completerToCursor()
|
||||||
|
|
||||||
|
def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QRect | QVariant:
|
||||||
|
"""Adjust completion windows for CJK input methods to consider
|
||||||
|
the viewport margins.
|
||||||
|
"""
|
||||||
|
if query == QtImCursorRectangle:
|
||||||
|
# See issues #2267 and #2517
|
||||||
|
vM = self.viewportMargins()
|
||||||
|
rect = self.cursorRect()
|
||||||
|
rect.translate(vM.left(), vM.top())
|
||||||
|
return rect
|
||||||
|
return super().inputMethodQuery(query)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Public Slots
|
# Public Slots
|
||||||
##
|
##
|
||||||
@@ -1086,15 +1108,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
# at unwanted times when other changes are made to the document
|
# at unwanted times when other changes are made to the document
|
||||||
cursor = self.textCursor()
|
cursor = self.textCursor()
|
||||||
bPos = cursor.positionInBlock()
|
bPos = cursor.positionInBlock()
|
||||||
if bPos > 0 and (viewport := self.viewport()):
|
if bPos > 0:
|
||||||
if text[0] == "@":
|
if text[0] == "@":
|
||||||
show = self._completer.updateMetaText(text, bPos)
|
show = self._completer.updateMetaText(text, bPos)
|
||||||
else:
|
else:
|
||||||
show = self._completer.updateCommentText(text, bPos)
|
show = self._completer.updateCommentText(text, bPos)
|
||||||
if show:
|
if show:
|
||||||
point = self.cursorRect().bottomRight()
|
|
||||||
self._completer.move(viewport.mapToGlobal(point))
|
|
||||||
self._completer.show()
|
self._completer.show()
|
||||||
|
self._completerToCursor()
|
||||||
|
|
||||||
if self._doReplace and added == 1:
|
if self._doReplace and added == 1:
|
||||||
cursor = self.textCursor()
|
cursor = self.textCursor()
|
||||||
@@ -1895,6 +1916,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def _completerToCursor(self) -> None:
|
||||||
|
"""Make sure the completer menu is positioned by the cursor."""
|
||||||
|
if self._completer.isVisible() and (viewport := self.viewport()):
|
||||||
|
point = self.cursorRect().bottomLeft()
|
||||||
|
self._completer.move(viewport.mapToGlobal(point))
|
||||||
|
|
||||||
def _correctWord(self, cursor: QTextCursor, word: str) -> None:
|
def _correctWord(self, cursor: QTextCursor, word: str) -> None:
|
||||||
"""Slot for the spell check context menu triggering the
|
"""Slot for the spell check context menu triggering the
|
||||||
replacement of a word with the word from the dictionary.
|
replacement of a word with the word from the dictionary.
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor
|
|||||||
QtMoveLeft = QTextCursor.MoveOperation.Left
|
QtMoveLeft = QTextCursor.MoveOperation.Left
|
||||||
QtMoveRight = QTextCursor.MoveOperation.Right
|
QtMoveRight = QTextCursor.MoveOperation.Right
|
||||||
|
|
||||||
|
QtImCursorRectangle = Qt.InputMethodQuery.ImCursorRectangle
|
||||||
|
|
||||||
# Size Policy
|
# Size Policy
|
||||||
|
|
||||||
QtSizeExpanding = QSizePolicy.Policy.Expanding
|
QtSizeExpanding = QSizePolicy.Policy.Expanding
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import pytest
|
|||||||
from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl
|
from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent,
|
QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent,
|
||||||
QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption
|
QDropEvent, QFont, QInputMethodEvent, QMouseEvent, QTextBlock, QTextCursor,
|
||||||
|
QTextOption
|
||||||
)
|
)
|
||||||
from PyQt6.QtWidgets import QApplication, QMenu, QPlainTextEdit
|
from PyQt6.QtWidgets import QApplication, QMenu, QPlainTextEdit
|
||||||
|
|
||||||
@@ -1941,6 +1942,19 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
"%Note.Consistency: \n"
|
"%Note.Consistency: \n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# CJK completer reposition (#2267 and #2517)
|
||||||
|
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||||
|
assert completer.isVisible() is True
|
||||||
|
completer.move(0, 0)
|
||||||
|
assert completer.pos().x() == 0 # Completer menu at 0
|
||||||
|
assert completer.pos().y() == 0 # Completer menu at 0
|
||||||
|
|
||||||
|
event = QInputMethodEvent()
|
||||||
|
event.setCommitString("Text")
|
||||||
|
docEditor.inputMethodEvent(event)
|
||||||
|
assert completer.pos().x() > 0 # Completer should have moved
|
||||||
|
assert completer.pos().y() > 0 # Completer should have moved
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user