Adjust windows for IME conversion suggestions to not cover text (#2518)

This commit is contained in:
Veronica Berglyd Olsen
2025-09-13 18:47:29 +02:00
committed by GitHub
3 changed files with 55 additions and 12 deletions
+38 -11
View File
@@ -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.
+2
View File
@@ -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
+15 -1
View File
@@ -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()