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