|
39 | 39 | from time import time |
40 | 40 |
|
41 | 41 | from PyQt6.QtCore import ( |
42 | | - QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, |
43 | | - pyqtSlot |
| 42 | + QObject, QPoint, QRect, QRegularExpression, QRunnable, Qt, QTimer, |
| 43 | + QVariant, pyqtSignal, pyqtSlot |
44 | 44 | ) |
45 | 45 | from PyQt6.QtGui import ( |
46 | | - QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, |
47 | | - QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut, |
48 | | - QTextBlock, QTextCursor, QTextDocument, QTextOption |
| 46 | + QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, |
| 47 | + QInputMethodEvent, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, |
| 48 | + QResizeEvent, QShortcut, QTextBlock, QTextCursor, QTextDocument, |
| 49 | + QTextOption |
49 | 50 | ) |
50 | 51 | from PyQt6.QtWidgets import ( |
51 | 52 | QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, |
|
74 | 75 | from novelwriter.tools.lipsum import GuiLipsum |
75 | 76 | from novelwriter.types import ( |
76 | 77 | QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop, |
77 | | - QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, |
78 | | - QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded |
| 78 | + QtAlignRight, QtImCursorRectangle, QtKeepAnchor, QtModCtrl, QtModNone, |
| 79 | + QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveLeft, QtMoveRight, |
| 80 | + QtScrollAlwaysOff, QtScrollAsNeeded |
79 | 81 | ) |
80 | 82 |
|
81 | 83 | logger = logging.getLogger(__name__) |
@@ -914,7 +916,7 @@ def insertNewBlock(self, text: str, defaultAfter: bool = True) -> bool: |
914 | 916 | return True |
915 | 917 |
|
916 | 918 | ## |
917 | | - # Document Events and Maintenance |
| 919 | + # Events and Overloads |
918 | 920 | ## |
919 | 921 |
|
920 | 922 | def keyPressEvent(self, event: QKeyEvent) -> None: |
@@ -1017,6 +1019,26 @@ def resizeEvent(self, event: QResizeEvent) -> None: |
1017 | 1019 | super().resizeEvent(event) |
1018 | 1020 | return |
1019 | 1021 |
|
| 1022 | + def inputMethodEvent(self, event: QInputMethodEvent) -> None: |
| 1023 | + """Handle text being input from CJK input methods.""" |
| 1024 | + super().inputMethodEvent(event) |
| 1025 | + if event.commitString(): |
| 1026 | + # See issues #2267 and #2517 |
| 1027 | + self.ensureCursorVisible() |
| 1028 | + self._completerToCursor() |
| 1029 | + |
| 1030 | + def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QRect | QVariant: |
| 1031 | + """Adjust completion windows for CJK input methods to consider |
| 1032 | + the viewport margins. |
| 1033 | + """ |
| 1034 | + if query == QtImCursorRectangle: |
| 1035 | + # See issues #2267 and #2517 |
| 1036 | + vM = self.viewportMargins() |
| 1037 | + rect = self.cursorRect() |
| 1038 | + rect.translate(vM.left(), vM.top()) |
| 1039 | + return rect |
| 1040 | + return super().inputMethodQuery(query) |
| 1041 | + |
1020 | 1042 | ## |
1021 | 1043 | # Public Slots |
1022 | 1044 | ## |
@@ -1086,15 +1108,14 @@ def _docChange(self, pos: int, removed: int, added: int) -> None: |
1086 | 1108 | # at unwanted times when other changes are made to the document |
1087 | 1109 | cursor = self.textCursor() |
1088 | 1110 | bPos = cursor.positionInBlock() |
1089 | | - if bPos > 0 and (viewport := self.viewport()): |
| 1111 | + if bPos > 0: |
1090 | 1112 | if text[0] == "@": |
1091 | 1113 | show = self._completer.updateMetaText(text, bPos) |
1092 | 1114 | else: |
1093 | 1115 | show = self._completer.updateCommentText(text, bPos) |
1094 | 1116 | if show: |
1095 | | - point = self.cursorRect().bottomRight() |
1096 | | - self._completer.move(viewport.mapToGlobal(point)) |
1097 | 1117 | self._completer.show() |
| 1118 | + self._completerToCursor() |
1098 | 1119 |
|
1099 | 1120 | if self._doReplace and added == 1: |
1100 | 1121 | cursor = self.textCursor() |
@@ -1895,6 +1916,12 @@ def _insertCommentStructure(self, style: nwComment) -> None: |
1895 | 1916 | # Internal Functions |
1896 | 1917 | ## |
1897 | 1918 |
|
| 1919 | + def _completerToCursor(self) -> None: |
| 1920 | + """Make sure the completer menu is positioned by the cursor.""" |
| 1921 | + if self._completer.isVisible() and (viewport := self.viewport()): |
| 1922 | + point = self.cursorRect().bottomLeft() |
| 1923 | + self._completer.move(viewport.mapToGlobal(point)) |
| 1924 | + |
1898 | 1925 | def _correctWord(self, cursor: QTextCursor, word: str) -> None: |
1899 | 1926 | """Slot for the spell check context menu triggering the |
1900 | 1927 | replacement of a word with the word from the dictionary. |
|
0 commit comments