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
24constexpr int IndicatorInput = static_cast<int>(Scintilla::IndicatorNumbers::Ime);
25constexpr int IndicatorTarget = IndicatorInput + 1;
26constexpr int IndicatorConverted = IndicatorInput + 2;
27constexpr 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
40using namespace Scintilla;
41using namespace Scintilla::Internal;
42
43ScintillaEditBase::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
93ScintillaEditBase::~ScintillaEditBase() = default;
94
95sptr_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
103sptr_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
111void ScintillaEditBase::scrollHorizontal(int value)
112{
113 sqt->HorizontalScrollTo(value);
114}
115
116void ScintillaEditBase::scrollVertical(int value)
117{
118 sqt->ScrollTo(value);
119}
120
121bool 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
142void ScintillaEditBase::paintEvent(QPaintEvent *event)
143{
144 sqt->PartialPaint(PRectFromQRect(event->rect()));
145}
146
147namespace {
148
149bool 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
157int 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
167void 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
195void ScintillaEditBase::focusInEvent(QFocusEvent *event)
196{
197 sqt->SetFocusState(true);
198
199 QAbstractScrollArea::focusInEvent(event);
200}
201
202void ScintillaEditBase::focusOutEvent(QFocusEvent *event)
203{
204 sqt->SetFocusState(false);
205
206 QAbstractScrollArea::focusOutEvent(event);
207}
208
209void ScintillaEditBase::resizeEvent(QResizeEvent *)
210{
211 sqt->ChangeSize();
212 emit resized();
213}
214
215void 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
288static 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
305void 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
340void 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
354void ScintillaEditBase::mouseDoubleClickEvent(QMouseEvent *event)
355{
356 // Scintilla does its own double-click detection.
357 mousePressEvent(event);
358}
359
360void 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
379void ScintillaEditBase::contextMenuEvent(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
391void 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
405void ScintillaEditBase::dragLeaveEvent(QDragLeaveEvent * /* event */)
406{
407 sqt->DragLeave();
408}
409
410void 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
424void 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
441bool 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
455void 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
465void 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
481static 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
490static 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
538void 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
623QVariant 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 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
700void 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
827void ScintillaEditBase::event_command(uptr_t wParam, sptr_t lParam)
828{
829 emit command(wParam, lParam);
830}
831
832KeyMod 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