1 | // |
2 | // Copyright (c) 1990-2011, Scientific Toolworks, Inc. |
3 | // |
4 | // The License.txt file describes the conditions under which this software may be distributed. |
5 | // |
6 | // Author: Jason Haslam |
7 | // |
8 | // Additions Copyright (c) 2011 Archaeopteryx Software, Inc. d/b/a Wingware |
9 | // @file ScintillaEditBase.cpp - Qt widget that wraps ScintillaQt and provides events and scrolling |
10 | |
11 | #include "ScintillaEditBase.h" |
12 | #include "ScintillaQt.h" |
13 | #include "PlatQt.h" |
14 | |
15 | #include <QApplication> |
16 | #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) |
17 | #include <QInputContext> |
18 | #endif |
19 | #include <QPainter> |
20 | #include <QVarLengthArray> |
21 | #include <QScrollBar> |
22 | #include <QTextFormat> |
23 | |
24 | constexpr int IndicatorInput = static_cast<int>(Scintilla::IndicatorNumbers::Ime); |
25 | constexpr int IndicatorTarget = IndicatorInput + 1; |
26 | constexpr int IndicatorConverted = IndicatorInput + 2; |
27 | constexpr int IndicatorUnknown = IndicatorInput + 3; |
28 | |
29 | // Q_WS_MAC and Q_WS_X11 aren't defined in Qt5 |
30 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) |
31 | #ifdef Q_OS_MAC |
32 | #define Q_WS_MAC 1 |
33 | #endif |
34 | |
35 | #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) |
36 | #define Q_WS_X11 1 |
37 | #endif |
38 | #endif // QT_VERSION >= 5.0.0 |
39 | |
40 | using namespace Scintilla; |
41 | using namespace Scintilla::Internal; |
42 | |
43 | ScintillaEditBase::ScintillaEditBase(QWidget *parent) |
44 | : QAbstractScrollArea(parent), sqt(new ScintillaQt(this)), preeditPos(-1), wheelDelta(0) |
45 | { |
46 | time.start(); |
47 | |
48 | // Set Qt defaults. |
49 | setAcceptDrops(true); |
50 | setMouseTracking(true); |
51 | setAutoFillBackground(false); |
52 | setFrameStyle(QFrame::NoFrame); |
53 | setFocusPolicy(Qt::StrongFocus); |
54 | setAttribute(Qt::WA_StaticContents); |
55 | viewport()->setAutoFillBackground(false); |
56 | setAttribute(Qt::WA_KeyCompression); |
57 | setAttribute(Qt::WA_InputMethodEnabled); |
58 | |
59 | sqt->vs.indicators[IndicatorUnknown] = Indicator(IndicatorStyle::Hidden, ColourRGBA(0, 0, 0xff)); |
60 | sqt->vs.indicators[IndicatorInput] = Indicator(IndicatorStyle::Dots, ColourRGBA(0, 0, 0xff)); |
61 | sqt->vs.indicators[IndicatorConverted] = Indicator(IndicatorStyle::CompositionThick, ColourRGBA(0, 0, 0xff)); |
62 | sqt->vs.indicators[IndicatorTarget] = Indicator(IndicatorStyle::StraightBox, ColourRGBA(0, 0, 0xff)); |
63 | |
64 | connect(sqt, SIGNAL(notifyParent(Scintilla::NotificationData)), |
65 | this, SLOT(notifyParent(Scintilla::NotificationData))); |
66 | |
67 | // Connect scroll bars. |
68 | connect(verticalScrollBar(), SIGNAL(valueChanged(int)), |
69 | this, SLOT(scrollVertical(int))); |
70 | connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), |
71 | this, SLOT(scrollHorizontal(int))); |
72 | |
73 | // Connect pass-through signals. |
74 | connect(sqt, SIGNAL(horizontalRangeChanged(int,int)), |
75 | this, SIGNAL(horizontalRangeChanged(int,int))); |
76 | connect(sqt, SIGNAL(verticalRangeChanged(int,int)), |
77 | this, SIGNAL(verticalRangeChanged(int,int))); |
78 | connect(sqt, SIGNAL(horizontalScrolled(int)), |
79 | this, SIGNAL(horizontalScrolled(int))); |
80 | connect(sqt, SIGNAL(verticalScrolled(int)), |
81 | this, SIGNAL(verticalScrolled(int))); |
82 | |
83 | connect(sqt, SIGNAL(notifyChange()), |
84 | this, SIGNAL(notifyChange())); |
85 | |
86 | connect(sqt, SIGNAL(command(Scintilla::uptr_t,Scintilla::sptr_t)), |
87 | this, SLOT(event_command(Scintilla::uptr_t,Scintilla::sptr_t))); |
88 | |
89 | connect(sqt, SIGNAL(aboutToCopy(QMimeData*)), |
90 | this, SIGNAL(aboutToCopy(QMimeData*))); |
91 | } |
92 | |
93 | ScintillaEditBase::~ScintillaEditBase() = default; |
94 | |
95 | sptr_t ScintillaEditBase::send( |
96 | unsigned int iMessage, |
97 | uptr_t wParam, |
98 | sptr_t lParam) const |
99 | { |
100 | return sqt->WndProc(static_cast<Message>(iMessage), wParam, lParam); |
101 | } |
102 | |
103 | sptr_t ScintillaEditBase::sends( |
104 | unsigned int iMessage, |
105 | uptr_t wParam, |
106 | const char *s) const |
107 | { |
108 | return sqt->WndProc(static_cast<Message>(iMessage), wParam, reinterpret_cast<sptr_t>(s)); |
109 | } |
110 | |
111 | void ScintillaEditBase::scrollHorizontal(int value) |
112 | { |
113 | sqt->HorizontalScrollTo(value); |
114 | } |
115 | |
116 | void ScintillaEditBase::scrollVertical(int value) |
117 | { |
118 | sqt->ScrollTo(value); |
119 | } |
120 | |
121 | bool ScintillaEditBase::event(QEvent *event) |
122 | { |
123 | bool result = false; |
124 | |
125 | if (event->type() == QEvent::KeyPress) { |
126 | // Circumvent the tab focus convention. |
127 | keyPressEvent(static_cast<QKeyEvent *>(event)); |
128 | result = event->isAccepted(); |
129 | } else if (event->type() == QEvent::Show) { |
130 | setMouseTracking(true); |
131 | result = QAbstractScrollArea::event(event); |
132 | } else if (event->type() == QEvent::Hide) { |
133 | setMouseTracking(false); |
134 | result = QAbstractScrollArea::event(event); |
135 | } else { |
136 | result = QAbstractScrollArea::event(event); |
137 | } |
138 | |
139 | return result; |
140 | } |
141 | |
142 | void ScintillaEditBase::paintEvent(QPaintEvent *event) |
143 | { |
144 | sqt->PartialPaint(PRectFromQRect(event->rect())); |
145 | } |
146 | |
147 | namespace { |
148 | |
149 | bool isWheelEventHorizontal(QWheelEvent *event) { |
150 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
151 | return event->angleDelta().y() == 0; |
152 | #else |
153 | return event->orientation() == Qt::Horizontal; |
154 | #endif |
155 | } |
156 | |
157 | int wheelEventYDelta(QWheelEvent *event) { |
158 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
159 | return event->angleDelta().y(); |
160 | #else |
161 | return event->delta(); |
162 | #endif |
163 | } |
164 | |
165 | } |
166 | |
167 | void ScintillaEditBase::wheelEvent(QWheelEvent *event) |
168 | { |
169 | if (isWheelEventHorizontal(event)) { |
170 | if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) |
171 | event->ignore(); |
172 | else |
173 | QAbstractScrollArea::wheelEvent(event); |
174 | } else { |
175 | if (QApplication::keyboardModifiers() & Qt::ControlModifier) { |
176 | // Zoom! We play with the font sizes in the styles. |
177 | // Number of steps/line is ignored, we just care if sizing up or down |
178 | if (wheelEventYDelta(event) > 0) { |
179 | sqt->KeyCommand(Message::ZoomIn); |
180 | } else { |
181 | sqt->KeyCommand(Message::ZoomOut); |
182 | } |
183 | } else { |
184 | // Ignore wheel events when the scroll bars are disabled. |
185 | if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { |
186 | event->ignore(); |
187 | } else { |
188 | // Scroll |
189 | QAbstractScrollArea::wheelEvent(event); |
190 | } |
191 | } |
192 | } |
193 | } |
194 | |
195 | void ScintillaEditBase::focusInEvent(QFocusEvent *event) |
196 | { |
197 | sqt->SetFocusState(true); |
198 | |
199 | QAbstractScrollArea::focusInEvent(event); |
200 | } |
201 | |
202 | void ScintillaEditBase::focusOutEvent(QFocusEvent *event) |
203 | { |
204 | sqt->SetFocusState(false); |
205 | |
206 | QAbstractScrollArea::focusOutEvent(event); |
207 | } |
208 | |
209 | void ScintillaEditBase::resizeEvent(QResizeEvent *) |
210 | { |
211 | sqt->ChangeSize(); |
212 | emit resized(); |
213 | } |
214 | |
215 | void ScintillaEditBase::keyPressEvent(QKeyEvent *event) |
216 | { |
217 | // All keystrokes containing the meta modifier are |
218 | // assumed to be shortcuts not handled by scintilla. |
219 | if (QApplication::keyboardModifiers() & Qt::MetaModifier) { |
220 | QAbstractScrollArea::keyPressEvent(event); |
221 | emit keyPressed(event); |
222 | return; |
223 | } |
224 | |
225 | int key = 0; |
226 | switch (event->key()) { |
227 | case Qt::Key_Down: key = SCK_DOWN; break; |
228 | case Qt::Key_Up: key = SCK_UP; break; |
229 | case Qt::Key_Left: key = SCK_LEFT; break; |
230 | case Qt::Key_Right: key = SCK_RIGHT; break; |
231 | case Qt::Key_Home: key = SCK_HOME; break; |
232 | case Qt::Key_End: key = SCK_END; break; |
233 | case Qt::Key_PageUp: key = SCK_PRIOR; break; |
234 | case Qt::Key_PageDown: key = SCK_NEXT; break; |
235 | case Qt::Key_Delete: key = SCK_DELETE; break; |
236 | case Qt::Key_Insert: key = SCK_INSERT; break; |
237 | case Qt::Key_Escape: key = SCK_ESCAPE; break; |
238 | case Qt::Key_Backspace: key = SCK_BACK; break; |
239 | case Qt::Key_Plus: key = SCK_ADD; break; |
240 | case Qt::Key_Minus: key = SCK_SUBTRACT; break; |
241 | case Qt::Key_Backtab: // fall through |
242 | case Qt::Key_Tab: key = SCK_TAB; break; |
243 | case Qt::Key_Enter: // fall through |
244 | case Qt::Key_Return: key = SCK_RETURN; break; |
245 | case Qt::Key_Control: key = 0; break; |
246 | case Qt::Key_Alt: key = 0; break; |
247 | case Qt::Key_Shift: key = 0; break; |
248 | case Qt::Key_Meta: key = 0; break; |
249 | default: key = event->key(); break; |
250 | } |
251 | |
252 | bool shift = QApplication::keyboardModifiers() & Qt::ShiftModifier; |
253 | bool ctrl = QApplication::keyboardModifiers() & Qt::ControlModifier; |
254 | bool alt = QApplication::keyboardModifiers() & Qt::AltModifier; |
255 | |
256 | bool consumed = false; |
257 | bool added = sqt->KeyDownWithModifiers(static_cast<Keys>(key), |
258 | ScintillaQt::ModifierFlags(shift, ctrl, alt), |
259 | &consumed) != 0; |
260 | if (!consumed) |
261 | consumed = added; |
262 | |
263 | if (!consumed) { |
264 | // Don't insert text if the control key was pressed unless |
265 | // it was pressed in conjunction with alt for AltGr emulation. |
266 | bool input = (!ctrl || alt); |
267 | |
268 | // Additionally, on non-mac platforms, don't insert text |
269 | // if the alt key was pressed unless control is also present. |
270 | // On mac alt can be used to insert special characters. |
271 | #ifndef Q_WS_MAC |
272 | input &= (!alt || ctrl); |
273 | #endif |
274 | |
275 | QString text = event->text(); |
276 | if (input && !text.isEmpty() && text[0].isPrint()) { |
277 | QByteArray utext = sqt->BytesForDocument(text); |
278 | sqt->InsertCharacter(std::string_view(utext.data(), utext.size()), CharacterSource::DirectInput); |
279 | } else { |
280 | event->ignore(); |
281 | } |
282 | } |
283 | |
284 | emit keyPressed(event); |
285 | } |
286 | |
287 | #ifdef Q_WS_X11 |
288 | static int modifierTranslated(int sciModifier) |
289 | { |
290 | switch (sciModifier) { |
291 | case SCMOD_SHIFT: |
292 | return Qt::ShiftModifier; |
293 | case SCMOD_CTRL: |
294 | return Qt::ControlModifier; |
295 | case SCMOD_ALT: |
296 | return Qt::AltModifier; |
297 | case SCMOD_SUPER: |
298 | return Qt::MetaModifier; |
299 | default: |
300 | return 0; |
301 | } |
302 | } |
303 | #endif |
304 | |
305 | void ScintillaEditBase::mousePressEvent(QMouseEvent *event) |
306 | { |
307 | Point pos = PointFromQPoint(event->pos()); |
308 | |
309 | emit buttonPressed(event); |
310 | |
311 | if (event->button() == Qt::MiddleButton && |
312 | QApplication::clipboard()->supportsSelection()) { |
313 | SelectionPosition selPos = sqt->SPositionFromLocation( |
314 | pos, false, false, sqt->UserVirtualSpace()); |
315 | sqt->sel.Clear(); |
316 | sqt->SetSelection(selPos, selPos); |
317 | sqt->PasteFromMode(QClipboard::Selection); |
318 | return; |
319 | } |
320 | |
321 | if (event->button() == Qt::LeftButton) { |
322 | bool shift = QApplication::keyboardModifiers() & Qt::ShiftModifier; |
323 | bool ctrl = QApplication::keyboardModifiers() & Qt::ControlModifier; |
324 | #ifdef Q_WS_X11 |
325 | // On X allow choice of rectangular modifier since most window |
326 | // managers grab alt + click for moving windows. |
327 | bool alt = QApplication::keyboardModifiers() & modifierTranslated(sqt->rectangularSelectionModifier); |
328 | #else |
329 | bool alt = QApplication::keyboardModifiers() & Qt::AltModifier; |
330 | #endif |
331 | |
332 | sqt->ButtonDownWithModifiers(pos, time.elapsed(), ScintillaQt::ModifierFlags(shift, ctrl, alt)); |
333 | } |
334 | |
335 | if (event->button() == Qt::RightButton) { |
336 | sqt->RightButtonDownWithModifiers(pos, time.elapsed(), ModifiersOfKeyboard()); |
337 | } |
338 | } |
339 | |
340 | void ScintillaEditBase::mouseReleaseEvent(QMouseEvent *event) |
341 | { |
342 | Point point = PointFromQPoint(event->pos()); |
343 | if (event->button() == Qt::LeftButton) |
344 | sqt->ButtonUpWithModifiers(point, time.elapsed(), ModifiersOfKeyboard()); |
345 | |
346 | int pos = send(SCI_POSITIONFROMPOINT, point.x, point.y); |
347 | sptr_t line = send(SCI_LINEFROMPOSITION, pos); |
348 | int modifiers = QApplication::keyboardModifiers(); |
349 | |
350 | emit textAreaClicked(line, modifiers); |
351 | emit buttonReleased(event); |
352 | } |
353 | |
354 | void ScintillaEditBase::mouseDoubleClickEvent(QMouseEvent *event) |
355 | { |
356 | // Scintilla does its own double-click detection. |
357 | mousePressEvent(event); |
358 | } |
359 | |
360 | void ScintillaEditBase::mouseMoveEvent(QMouseEvent *event) |
361 | { |
362 | Point pos = PointFromQPoint(event->pos()); |
363 | |
364 | bool shift = QApplication::keyboardModifiers() & Qt::ShiftModifier; |
365 | bool ctrl = QApplication::keyboardModifiers() & Qt::ControlModifier; |
366 | #ifdef Q_WS_X11 |
367 | // On X allow choice of rectangular modifier since most window |
368 | // managers grab alt + click for moving windows. |
369 | bool alt = QApplication::keyboardModifiers() & modifierTranslated(sqt->rectangularSelectionModifier); |
370 | #else |
371 | bool alt = QApplication::keyboardModifiers() & Qt::AltModifier; |
372 | #endif |
373 | |
374 | const KeyMod modifiers = ScintillaQt::ModifierFlags(shift, ctrl, alt); |
375 | |
376 | sqt->ButtonMoveWithModifiers(pos, time.elapsed(), modifiers); |
377 | } |
378 | |
379 | void ScintillaEditBase::(QContextMenuEvent *event) |
380 | { |
381 | Point pos = PointFromQPoint(event->globalPos()); |
382 | Point pt = PointFromQPoint(event->pos()); |
383 | if (!sqt->PointInSelection(pt)) { |
384 | sqt->SetEmptySelection(sqt->PositionFromLocation(pt)); |
385 | } |
386 | if (sqt->ShouldDisplayPopup(pt)) { |
387 | sqt->ContextMenu(pos); |
388 | } |
389 | } |
390 | |
391 | void ScintillaEditBase::dragEnterEvent(QDragEnterEvent *event) |
392 | { |
393 | if (event->mimeData()->hasUrls()) { |
394 | event->acceptProposedAction(); |
395 | } else if (event->mimeData()->hasText()) { |
396 | event->acceptProposedAction(); |
397 | |
398 | Point point = PointFromQPoint(event->pos()); |
399 | sqt->DragEnter(point); |
400 | } else { |
401 | event->ignore(); |
402 | } |
403 | } |
404 | |
405 | void ScintillaEditBase::dragLeaveEvent(QDragLeaveEvent * /* event */) |
406 | { |
407 | sqt->DragLeave(); |
408 | } |
409 | |
410 | void ScintillaEditBase::dragMoveEvent(QDragMoveEvent *event) |
411 | { |
412 | if (event->mimeData()->hasUrls()) { |
413 | event->acceptProposedAction(); |
414 | } else if (event->mimeData()->hasText()) { |
415 | event->acceptProposedAction(); |
416 | |
417 | Point point = PointFromQPoint(event->pos()); |
418 | sqt->DragMove(point); |
419 | } else { |
420 | event->ignore(); |
421 | } |
422 | } |
423 | |
424 | void ScintillaEditBase::dropEvent(QDropEvent *event) |
425 | { |
426 | if (event->mimeData()->hasUrls()) { |
427 | event->acceptProposedAction(); |
428 | sqt->DropUrls(event->mimeData()); |
429 | } else if (event->mimeData()->hasText()) { |
430 | event->acceptProposedAction(); |
431 | |
432 | Point point = PointFromQPoint(event->pos()); |
433 | bool move = (event->source() == this && |
434 | event->proposedAction() == Qt::MoveAction); |
435 | sqt->Drop(point, event->mimeData(), move); |
436 | } else { |
437 | event->ignore(); |
438 | } |
439 | } |
440 | |
441 | bool ScintillaEditBase::IsHangul(const QChar qchar) |
442 | { |
443 | int unicode = (int)qchar.unicode(); |
444 | // Korean character ranges used for preedit chars. |
445 | // http://www.programminginkorean.com/programming/hangul-in-unicode/ |
446 | const bool HangulJamo = (0x1100 <= unicode && unicode <= 0x11FF); |
447 | const bool HangulCompatibleJamo = (0x3130 <= unicode && unicode <= 0x318F); |
448 | const bool HangulJamoExtendedA = (0xA960 <= unicode && unicode <= 0xA97F); |
449 | const bool HangulJamoExtendedB = (0xD7B0 <= unicode && unicode <= 0xD7FF); |
450 | const bool HangulSyllable = (0xAC00 <= unicode && unicode <= 0xD7A3); |
451 | return HangulJamo || HangulCompatibleJamo || HangulSyllable || |
452 | HangulJamoExtendedA || HangulJamoExtendedB; |
453 | } |
454 | |
455 | void ScintillaEditBase::MoveImeCarets(int offset) |
456 | { |
457 | // Move carets relatively by bytes |
458 | for (size_t r=0; r < sqt->sel.Count(); r++) { |
459 | int positionInsert = sqt->sel.Range(r).Start().Position(); |
460 | sqt->sel.Range(r).caret.SetPosition(positionInsert + offset); |
461 | sqt->sel.Range(r).anchor.SetPosition(positionInsert + offset); |
462 | } |
463 | } |
464 | |
465 | void ScintillaEditBase::DrawImeIndicator(int indicator, int len) |
466 | { |
467 | // Emulate the visual style of IME characters with indicators. |
468 | // Draw an indicator on the character before caret by the character bytes of len |
469 | // so it should be called after InsertCharacter(). |
470 | // It does not affect caret positions. |
471 | if (indicator < 8 || indicator > INDICATOR_MAX) { |
472 | return; |
473 | } |
474 | sqt->pdoc->DecorationSetCurrentIndicator(indicator); |
475 | for (size_t r=0; r< sqt-> sel.Count(); r++) { |
476 | int positionInsert = sqt->sel.Range(r).Start().Position(); |
477 | sqt->pdoc->DecorationFillRange(positionInsert - len, 1, len); |
478 | } |
479 | } |
480 | |
481 | static int GetImeCaretPos(QInputMethodEvent *event) |
482 | { |
483 | foreach (QInputMethodEvent::Attribute attr, event->attributes()) { |
484 | if (attr.type == QInputMethodEvent::Cursor) |
485 | return attr.start; |
486 | } |
487 | return 0; |
488 | } |
489 | |
490 | static std::vector<int> MapImeIndicators(QInputMethodEvent *event) |
491 | { |
492 | std::vector<int> imeIndicator(event->preeditString().size(), IndicatorUnknown); |
493 | foreach (QInputMethodEvent::Attribute attr, event->attributes()) { |
494 | if (attr.type == QInputMethodEvent::TextFormat) { |
495 | QTextFormat format = attr.value.value<QTextFormat>(); |
496 | QTextCharFormat charFormat = format.toCharFormat(); |
497 | |
498 | int indicator = IndicatorUnknown; |
499 | switch (charFormat.underlineStyle()) { |
500 | case QTextCharFormat::NoUnderline: // win32, linux |
501 | indicator = IndicatorTarget; |
502 | break; |
503 | case QTextCharFormat::SingleUnderline: // osx |
504 | case QTextCharFormat::DashUnderline: // win32, linux |
505 | indicator = IndicatorInput; |
506 | break; |
507 | case QTextCharFormat::DotLine: |
508 | case QTextCharFormat::DashDotLine: |
509 | case QTextCharFormat::WaveUnderline: |
510 | case QTextCharFormat::SpellCheckUnderline: |
511 | indicator = IndicatorConverted; |
512 | break; |
513 | |
514 | default: |
515 | indicator = IndicatorUnknown; |
516 | } |
517 | |
518 | if (format.hasProperty(QTextFormat::BackgroundBrush)) // win32, linux |
519 | indicator = IndicatorTarget; |
520 | |
521 | #ifdef Q_OS_OSX |
522 | if (charFormat.underlineStyle() == QTextCharFormat::SingleUnderline) { |
523 | QColor uc = charFormat.underlineColor(); |
524 | if (uc.lightness() < 2) { // osx |
525 | indicator = IndicatorTarget; |
526 | } |
527 | } |
528 | #endif |
529 | |
530 | for (int i = attr.start; i < attr.start+attr.length; i++) { |
531 | imeIndicator[i] = indicator; |
532 | } |
533 | } |
534 | } |
535 | return imeIndicator; |
536 | } |
537 | |
538 | void ScintillaEditBase::inputMethodEvent(QInputMethodEvent *event) |
539 | { |
540 | // Copy & paste by johnsonj with a lot of helps of Neil |
541 | // Great thanks for my forerunners, jiniya and BLUEnLIVE |
542 | |
543 | if (sqt->pdoc->IsReadOnly() || sqt->SelectionContainsProtected()) { |
544 | // Here, a canceling and/or completing composition function is needed. |
545 | return; |
546 | } |
547 | |
548 | bool initialCompose = false; |
549 | if (sqt->pdoc->TentativeActive()) { |
550 | sqt->pdoc->TentativeUndo(); |
551 | } else { |
552 | // No tentative undo means start of this composition so |
553 | // Fill in any virtual spaces. |
554 | initialCompose = true; |
555 | } |
556 | |
557 | sqt->view.imeCaretBlockOverride = false; |
558 | |
559 | if (!event->commitString().isEmpty()) { |
560 | const QString &commitStr = event->commitString(); |
561 | const unsigned int commitStrLen = commitStr.length(); |
562 | |
563 | for (unsigned int i = 0; i < commitStrLen;) { |
564 | const unsigned int ucWidth = commitStr.at(i).isHighSurrogate() ? 2 : 1; |
565 | const QString oneCharUTF16 = commitStr.mid(i, ucWidth); |
566 | const QByteArray oneChar = sqt->BytesForDocument(oneCharUTF16); |
567 | |
568 | sqt->InsertCharacter(std::string_view(oneChar.data(), oneChar.length()), CharacterSource::DirectInput); |
569 | i += ucWidth; |
570 | } |
571 | |
572 | } else if (!event->preeditString().isEmpty()) { |
573 | const QString preeditStr = event->preeditString(); |
574 | const unsigned int preeditStrLen = preeditStr.length(); |
575 | if (preeditStrLen == 0) { |
576 | sqt->ShowCaretAtCurrentPosition(); |
577 | return; |
578 | } |
579 | |
580 | if (initialCompose) |
581 | sqt->ClearBeforeTentativeStart(); |
582 | sqt->pdoc->TentativeStart(); // TentativeActive() from now on. |
583 | |
584 | std::vector<int> imeIndicator = MapImeIndicators(event); |
585 | |
586 | for (unsigned int i = 0; i < preeditStrLen;) { |
587 | const unsigned int ucWidth = preeditStr.at(i).isHighSurrogate() ? 2 : 1; |
588 | const QString oneCharUTF16 = preeditStr.mid(i, ucWidth); |
589 | const QByteArray oneChar = sqt->BytesForDocument(oneCharUTF16); |
590 | const int oneCharLen = oneChar.length(); |
591 | |
592 | sqt->InsertCharacter(std::string_view(oneChar.data(), oneCharLen), CharacterSource::TentativeInput); |
593 | |
594 | DrawImeIndicator(imeIndicator[i], oneCharLen); |
595 | i += ucWidth; |
596 | } |
597 | |
598 | // Move IME carets. |
599 | int imeCaretPos = GetImeCaretPos(event); |
600 | int imeEndToImeCaretU16 = imeCaretPos - preeditStrLen; |
601 | int imeCaretPosDoc = sqt->pdoc->GetRelativePositionUTF16(sqt->CurrentPosition(), imeEndToImeCaretU16); |
602 | |
603 | MoveImeCarets(- sqt->CurrentPosition() + imeCaretPosDoc); |
604 | |
605 | if (IsHangul(preeditStr.at(0))) { |
606 | #ifndef Q_OS_WIN |
607 | if (imeCaretPos > 0) { |
608 | int oneCharBefore = sqt->pdoc->GetRelativePosition(sqt->CurrentPosition(), -1); |
609 | MoveImeCarets(- sqt->CurrentPosition() + oneCharBefore); |
610 | } |
611 | #endif |
612 | sqt->view.imeCaretBlockOverride = true; |
613 | } |
614 | |
615 | // Set candidate box position for Qt::ImMicroFocus. |
616 | preeditPos = sqt->CurrentPosition(); |
617 | sqt->EnsureCaretVisible(); |
618 | updateMicroFocus(); |
619 | } |
620 | sqt->ShowCaretAtCurrentPosition(); |
621 | } |
622 | |
623 | QVariant ScintillaEditBase::inputMethodQuery(Qt::InputMethodQuery query) const |
624 | { |
625 | int pos = send(SCI_GETCURRENTPOS); |
626 | int line = send(SCI_LINEFROMPOSITION, pos); |
627 | |
628 | switch (query) { |
629 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) |
630 | // Qt 5 renamed ImMicroFocus to ImCursorRectangle then deprecated |
631 | // ImMicroFocus. Its the same value (2) and same description. |
632 | case Qt::ImCursorRectangle: |
633 | { |
634 | int startPos = (preeditPos >= 0) ? preeditPos : pos; |
635 | Point pt = sqt->LocationFromPosition(startPos); |
636 | int width = send(SCI_GETCARETWIDTH); |
637 | int height = send(SCI_TEXTHEIGHT, line); |
638 | return QRect(pt.x, pt.y, width, height); |
639 | } |
640 | #else |
641 | case Qt::ImMicroFocus: |
642 | { |
643 | int startPos = (preeditPos >= 0) ? preeditPos : pos; |
644 | Point pt = sqt->LocationFromPosition(startPos); |
645 | int width = send(SCI_GETCARETWIDTH); |
646 | int height = send(SCI_TEXTHEIGHT, line); |
647 | return QRect(pt.x, pt.y, width, height); |
648 | } |
649 | #endif |
650 | |
651 | case Qt::ImFont: |
652 | { |
653 | char fontName[64]; |
654 | int style = send(SCI_GETSTYLEAT, pos); |
655 | int len = sends(SCI_STYLEGETFONT, style, fontName); |
656 | int size = send(SCI_STYLEGETSIZE, style); |
657 | bool italic = send(SCI_STYLEGETITALIC, style); |
658 | int weight = send(SCI_STYLEGETBOLD, style) ? QFont::Bold : -1; |
659 | return QFont(QString::fromUtf8(fontName, len), size, weight, italic); |
660 | } |
661 | |
662 | case Qt::ImCursorPosition: |
663 | { |
664 | int paraStart = sqt->pdoc->ParaUp(pos); |
665 | return pos - paraStart; |
666 | } |
667 | |
668 | case Qt::ImSurroundingText: |
669 | { |
670 | int paraStart = sqt->pdoc->ParaUp(pos); |
671 | int paraEnd = sqt->pdoc->ParaDown(pos); |
672 | QVarLengthArray<char,1024> buffer(paraEnd - paraStart + 1); |
673 | |
674 | Sci_CharacterRange charRange{}; |
675 | charRange.cpMin = paraStart; |
676 | charRange.cpMax = paraEnd; |
677 | |
678 | Sci_TextRange {}; |
679 | textRange.chrg = charRange; |
680 | textRange.lpstrText = buffer.data(); |
681 | |
682 | send(SCI_GETTEXTRANGE, 0, reinterpret_cast<sptr_t>(&textRange)); |
683 | |
684 | return sqt->StringFromDocument(buffer.constData()); |
685 | } |
686 | |
687 | case Qt::ImCurrentSelection: |
688 | { |
689 | QVarLengthArray<char,1024> buffer(send(SCI_GETSELTEXT)); |
690 | sends(SCI_GETSELTEXT, 0, buffer.data()); |
691 | |
692 | return sqt->StringFromDocument(buffer.constData()); |
693 | } |
694 | |
695 | default: |
696 | return QVariant(); |
697 | } |
698 | } |
699 | |
700 | void ScintillaEditBase::notifyParent(NotificationData scn) |
701 | { |
702 | emit notify(&scn); |
703 | switch (scn.nmhdr.code) { |
704 | case Notification::StyleNeeded: |
705 | emit styleNeeded(scn.position); |
706 | break; |
707 | |
708 | case Notification::CharAdded: |
709 | emit charAdded(scn.ch); |
710 | break; |
711 | |
712 | case Notification::SavePointReached: |
713 | emit savePointChanged(false); |
714 | break; |
715 | |
716 | case Notification::SavePointLeft: |
717 | emit savePointChanged(true); |
718 | break; |
719 | |
720 | case Notification::ModifyAttemptRO: |
721 | emit modifyAttemptReadOnly(); |
722 | break; |
723 | |
724 | case Notification::Key: |
725 | emit key(scn.ch); |
726 | break; |
727 | |
728 | case Notification::DoubleClick: |
729 | emit doubleClick(scn.position, scn.line); |
730 | break; |
731 | |
732 | case Notification::UpdateUI: |
733 | emit updateUi(scn.updated); |
734 | break; |
735 | |
736 | case Notification::Modified: |
737 | { |
738 | const bool added = FlagSet(scn.modificationType, ModificationFlags::InsertText); |
739 | const bool deleted = FlagSet(scn.modificationType, ModificationFlags::DeleteText); |
740 | |
741 | int length = send(SCI_GETTEXTLENGTH); |
742 | bool firstLineAdded = (added && length == 1) || |
743 | (deleted && length == 0); |
744 | |
745 | if (scn.linesAdded != 0) { |
746 | emit linesAdded(scn.linesAdded); |
747 | } else if (firstLineAdded) { |
748 | emit linesAdded(added ? 1 : -1); |
749 | } |
750 | |
751 | const QByteArray bytes = QByteArray::fromRawData(scn.text, scn.length); |
752 | emit modified(scn.modificationType, scn.position, scn.length, |
753 | scn.linesAdded, bytes, scn.line, |
754 | scn.foldLevelNow, scn.foldLevelPrev); |
755 | break; |
756 | } |
757 | |
758 | case Notification::MacroRecord: |
759 | emit macroRecord(scn.message, scn.wParam, scn.lParam); |
760 | break; |
761 | |
762 | case Notification::MarginClick: |
763 | emit marginClicked(scn.position, scn.modifiers, scn.margin); |
764 | break; |
765 | |
766 | case Notification::NeedShown: |
767 | emit needShown(scn.position, scn.length); |
768 | break; |
769 | |
770 | case Notification::Painted: |
771 | emit painted(); |
772 | break; |
773 | |
774 | case Notification::UserListSelection: |
775 | emit userListSelection(); |
776 | break; |
777 | |
778 | case Notification::URIDropped: |
779 | emit uriDropped(QString::fromUtf8(scn.text)); |
780 | break; |
781 | |
782 | case Notification::DwellStart: |
783 | emit dwellStart(scn.x, scn.y); |
784 | break; |
785 | |
786 | case Notification::DwellEnd: |
787 | emit dwellEnd(scn.x, scn.y); |
788 | break; |
789 | |
790 | case Notification::Zoom: |
791 | emit zoom(send(SCI_GETZOOM)); |
792 | break; |
793 | |
794 | case Notification::HotSpotClick: |
795 | emit hotSpotClick(scn.position, scn.modifiers); |
796 | break; |
797 | |
798 | case Notification::HotSpotDoubleClick: |
799 | emit hotSpotDoubleClick(scn.position, scn.modifiers); |
800 | break; |
801 | |
802 | case Notification::CallTipClick: |
803 | emit callTipClick(); |
804 | break; |
805 | |
806 | case Notification::AutoCSelection: |
807 | emit autoCompleteSelection(scn.lParam, QString::fromUtf8(scn.text)); |
808 | break; |
809 | |
810 | case Notification::AutoCCancelled: |
811 | emit autoCompleteCancelled(); |
812 | break; |
813 | |
814 | case Notification::FocusIn: |
815 | emit focusChanged(true); |
816 | break; |
817 | |
818 | case Notification::FocusOut: |
819 | emit focusChanged(false); |
820 | break; |
821 | |
822 | default: |
823 | return; |
824 | } |
825 | } |
826 | |
827 | void ScintillaEditBase::event_command(uptr_t wParam, sptr_t lParam) |
828 | { |
829 | emit command(wParam, lParam); |
830 | } |
831 | |
832 | KeyMod ScintillaEditBase::ModifiersOfKeyboard() |
833 | { |
834 | const bool shift = QApplication::keyboardModifiers() & Qt::ShiftModifier; |
835 | const bool ctrl = QApplication::keyboardModifiers() & Qt::ControlModifier; |
836 | const bool alt = QApplication::keyboardModifiers() & Qt::AltModifier; |
837 | |
838 | return ScintillaQt::ModifierFlags(shift, ctrl, alt); |
839 | } |
840 | |