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 // ScintillaQt.cpp - Qt specific subclass of ScintillaBase
10
11 #include "ScintillaQt.h"
12 #include "PlatQt.h"
13
14 #include <QApplication>
15 #include <QDrag>
16 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
17 #include <QInputContext>
18 #endif
19 #include <QMimeData>
20 #include <QMenu>
21 #include <QTextCodec>
22 #include <QScrollBar>
23 #include <QTimer>
24
25 using namespace Scintilla;
26
27
ScintillaQt(QAbstractScrollArea * parent)28 ScintillaQt::ScintillaQt(QAbstractScrollArea *parent)
29 : QObject(parent), scrollArea(parent), vMax(0), hMax(0), vPage(0), hPage(0),
30 haveMouseCapture(false), dragWasDropped(false)
31 {
32
33 wMain = scrollArea->viewport();
34
35 imeInteraction = imeInline;
36
37 // On OS X drawing text into a pixmap moves it around 1 pixel to
38 // the right compared to drawing it directly onto a window.
39 // Buffered drawing turned off by default to avoid this.
40 view.bufferedDraw = false;
41
42 Init();
43
44 for (TickReason tr = tickCaret; tr <= tickDwell; tr = static_cast<TickReason>(tr + 1)) {
45 timers[tr] = 0;
46 }
47 }
48
~ScintillaQt()49 ScintillaQt::~ScintillaQt()
50 {
51 CancelTimers();
52 ChangeIdle(false);
53 }
54
execCommand(QAction * action)55 void ScintillaQt::execCommand(QAction *action)
56 {
57 int command = action->data().toInt();
58 Command(command);
59 }
60
61 #if defined(Q_OS_WIN)
62 static const QString sMSDEVColumnSelect("MSDEVColumnSelect");
63 static const QString sWrappedMSDEVColumnSelect("application/x-qt-windows-mime;value=\"MSDEVColumnSelect\"");
64 static const QString sVSEditorLineCutCopy("VisualStudioEditorOperationsLineCutCopyClipboardTag");
65 static const QString sWrappedVSEditorLineCutCopy("application/x-qt-windows-mime;value=\"VisualStudioEditorOperationsLineCutCopyClipboardTag\"");
66 #elif defined(Q_OS_MAC)
67 static const QString sScintillaRecPboardType("com.scintilla.utf16-plain-text.rectangular");
68 static const QString sScintillaRecMimeType("text/x-scintilla.utf16-plain-text.rectangular");
69 #else
70 // Linux
71 static const QString sMimeRectangularMarker("text/x-rectangular-marker");
72 #endif
73
74 #if defined(Q_OS_MAC) && QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
75
76 class ScintillaRectangularMime : public QMacPasteboardMime {
77 public:
ScintillaRectangularMime()78 ScintillaRectangularMime() : QMacPasteboardMime(MIME_ALL) {
79 }
80
convertorName()81 QString convertorName() {
82 return QString("ScintillaRectangularMime");
83 }
84
canConvert(const QString & mime,QString flav)85 bool canConvert(const QString &mime, QString flav) {
86 return mimeFor(flav) == mime;
87 }
88
mimeFor(QString flav)89 QString mimeFor(QString flav) {
90 if (flav == sScintillaRecPboardType)
91 return sScintillaRecMimeType;
92 return QString();
93 }
94
flavorFor(const QString & mime)95 QString flavorFor(const QString &mime) {
96 if (mime == sScintillaRecMimeType)
97 return sScintillaRecPboardType;
98 return QString();
99 }
100
convertToMime(const QString &,QList<QByteArray> data,QString)101 QVariant convertToMime(const QString & /* mime */, QList<QByteArray> data, QString /* flav */) {
102 QByteArray all;
103 foreach (QByteArray i, data) {
104 all += i;
105 }
106 return QVariant(all);
107 }
108
convertFromMime(const QString &,QVariant data,QString)109 QList<QByteArray> convertFromMime(const QString & /* mime */, QVariant data, QString /* flav */) {
110 QByteArray a = data.toByteArray();
111 QList<QByteArray> l;
112 l.append(a);
113 return l;
114 }
115
116 };
117
118 // The Mime object registers itself but only want one for all Scintilla instances.
119 // Should delete at exit to help memory leak detection but that would be extra work
120 // and, since the clipboard exists after last Scintilla instance, may be complex.
121 static ScintillaRectangularMime *singletonMime = 0;
122
123 #endif
124
Init()125 void ScintillaQt::Init()
126 {
127 rectangularSelectionModifier = SCMOD_ALT;
128
129 #if defined(Q_OS_MAC) && QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
130 if (!singletonMime) {
131 singletonMime = new ScintillaRectangularMime();
132
133 QStringList slTypes(sScintillaRecPboardType);
134 qRegisterDraggedTypes(slTypes);
135 }
136 #endif
137
138 connect(QApplication::clipboard(), SIGNAL(selectionChanged()),
139 this, SLOT(SelectionChanged()));
140 }
141
Finalise()142 void ScintillaQt::Finalise()
143 {
144 CancelTimers();
145 ScintillaBase::Finalise();
146 }
147
SelectionChanged()148 void ScintillaQt::SelectionChanged()
149 {
150 bool nowPrimary = QApplication::clipboard()->ownsSelection();
151 if (nowPrimary != primarySelection) {
152 primarySelection = nowPrimary;
153 Redraw();
154 }
155 }
156
DragThreshold(Point ptStart,Point ptNow)157 bool ScintillaQt::DragThreshold(Point ptStart, Point ptNow)
158 {
159 int xMove = std::abs(ptStart.x - ptNow.x);
160 int yMove = std::abs(ptStart.y - ptNow.y);
161 return (xMove > QApplication::startDragDistance()) ||
162 (yMove > QApplication::startDragDistance());
163 }
164
StringFromSelectedText(const SelectionText & selectedText)165 static QString StringFromSelectedText(const SelectionText &selectedText)
166 {
167 if (selectedText.codePage == SC_CP_UTF8) {
168 return QString::fromUtf8(selectedText.Data(), static_cast<int>(selectedText.Length()));
169 } else {
170 QTextCodec *codec = QTextCodec::codecForName(
171 CharacterSetID(selectedText.characterSet));
172 return codec->toUnicode(selectedText.Data(), static_cast<int>(selectedText.Length()));
173 }
174 }
175
AddRectangularToMime(QMimeData * mimeData,QString su)176 static void AddRectangularToMime(QMimeData *mimeData, [[maybe_unused]] QString su)
177 {
178 #if defined(Q_OS_WIN)
179 // Add an empty marker
180 mimeData->setData(sMSDEVColumnSelect, QByteArray());
181 #elif defined(Q_OS_MAC)
182 // OS X gets marker + data to work with other implementations.
183 // Don't understand how this works but it does - the
184 // clipboard format is supposed to be UTF-16, not UTF-8.
185 mimeData->setData(sScintillaRecMimeType, su.toUtf8());
186 #else
187 // Linux
188 // Add an empty marker
189 mimeData->setData(sMimeRectangularMarker, QByteArray());
190 #endif
191 }
192
AddLineCutCopyToMime(QMimeData * mimeData)193 static void AddLineCutCopyToMime([[maybe_unused]] QMimeData *mimeData)
194 {
195 #if defined(Q_OS_WIN)
196 // Add an empty marker
197 mimeData->setData(sVSEditorLineCutCopy, QByteArray());
198 #endif
199 }
200
IsRectangularInMime(const QMimeData * mimeData)201 static bool IsRectangularInMime(const QMimeData *mimeData)
202 {
203 QStringList formats = mimeData->formats();
204 for (int i = 0; i < formats.size(); ++i) {
205 #if defined(Q_OS_WIN)
206 // Windows rectangular markers
207 // If rectangular copies made by this application, see base name.
208 if (formats[i] == sMSDEVColumnSelect)
209 return true;
210 // Otherwise see wrapped name.
211 if (formats[i] == sWrappedMSDEVColumnSelect)
212 return true;
213 #elif defined(Q_OS_MAC)
214 if (formats[i] == sScintillaRecMimeType)
215 return true;
216 #else
217 // Linux
218 if (formats[i] == sMimeRectangularMarker)
219 return true;
220 #endif
221 }
222 return false;
223 }
224
IsLineCutCopyInMime(const QMimeData * mimeData)225 static bool IsLineCutCopyInMime(const QMimeData *mimeData)
226 {
227 QStringList formats = mimeData->formats();
228 for (int i = 0; i < formats.size(); ++i) {
229 #if defined(Q_OS_WIN)
230 // Visual Studio Line Cut/Copy markers
231 // If line cut/copy made by this application, see base name.
232 if (formats[i] == sVSEditorLineCutCopy)
233 return true;
234 // Otherwise see wrapped name.
235 if (formats[i] == sWrappedVSEditorLineCutCopy)
236 return true;
237 #endif
238 }
239 return false;
240 }
241
ValidCodePage(int codePage) const242 bool ScintillaQt::ValidCodePage(int codePage) const
243 {
244 return codePage == 0
245 || codePage == SC_CP_UTF8
246 || codePage == 932
247 || codePage == 936
248 || codePage == 949
249 || codePage == 950
250 || codePage == 1361;
251 }
252
253
ScrollText(Sci::Line linesToMove)254 void ScintillaQt::ScrollText(Sci::Line linesToMove)
255 {
256 int dy = vs.lineHeight * (linesToMove);
257 scrollArea->viewport()->scroll(0, dy);
258 }
259
SetVerticalScrollPos()260 void ScintillaQt::SetVerticalScrollPos()
261 {
262 scrollArea->verticalScrollBar()->setValue(topLine);
263 emit verticalScrolled(topLine);
264 }
265
SetHorizontalScrollPos()266 void ScintillaQt::SetHorizontalScrollPos()
267 {
268 scrollArea->horizontalScrollBar()->setValue(xOffset);
269 emit horizontalScrolled(xOffset);
270 }
271
ModifyScrollBars(Sci::Line nMax,Sci::Line nPage)272 bool ScintillaQt::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage)
273 {
274 bool modified = false;
275
276 int vNewPage = nPage;
277 int vNewMax = nMax - vNewPage + 1;
278 if (vMax != vNewMax || vPage != vNewPage) {
279 vMax = vNewMax;
280 vPage = vNewPage;
281 modified = true;
282
283 scrollArea->verticalScrollBar()->setMaximum(vMax);
284 scrollArea->verticalScrollBar()->setPageStep(vPage);
285 emit verticalRangeChanged(vMax, vPage);
286 }
287
288 int hNewPage = GetTextRectangle().Width();
289 int hNewMax = (scrollWidth > hNewPage) ? scrollWidth - hNewPage : 0;
290 int charWidth = vs.styles[STYLE_DEFAULT].aveCharWidth;
291 if (hMax != hNewMax || hPage != hNewPage ||
292 scrollArea->horizontalScrollBar()->singleStep() != charWidth) {
293 hMax = hNewMax;
294 hPage = hNewPage;
295 modified = true;
296
297 scrollArea->horizontalScrollBar()->setMaximum(hMax);
298 scrollArea->horizontalScrollBar()->setPageStep(hPage);
299 scrollArea->horizontalScrollBar()->setSingleStep(charWidth);
300 emit horizontalRangeChanged(hMax, hPage);
301 }
302
303 return modified;
304 }
305
ReconfigureScrollBars()306 void ScintillaQt::ReconfigureScrollBars()
307 {
308 if (verticalScrollBarVisible) {
309 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
310 } else {
311 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
312 }
313
314 if (horizontalScrollBarVisible && !Wrapping()) {
315 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
316 } else {
317 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
318 }
319 }
320
CopyToModeClipboard(const SelectionText & selectedText,QClipboard::Mode clipboardMode_)321 void ScintillaQt::CopyToModeClipboard(const SelectionText &selectedText, QClipboard::Mode clipboardMode_)
322 {
323 QClipboard *clipboard = QApplication::clipboard();
324 QString su = StringFromSelectedText(selectedText);
325 QMimeData *mimeData = new QMimeData();
326 mimeData->setText(su);
327 if (selectedText.rectangular) {
328 AddRectangularToMime(mimeData, su);
329 }
330
331 if (selectedText.lineCopy) {
332 AddLineCutCopyToMime(mimeData);
333 }
334
335 // Allow client code to add additional data (e.g rich text).
336 emit aboutToCopy(mimeData);
337
338 clipboard->setMimeData(mimeData, clipboardMode_);
339 }
340
Copy()341 void ScintillaQt::Copy()
342 {
343 if (!sel.Empty()) {
344 SelectionText st;
345 CopySelectionRange(&st);
346 CopyToClipboard(st);
347 }
348 }
349
CopyToClipboard(const SelectionText & selectedText)350 void ScintillaQt::CopyToClipboard(const SelectionText &selectedText)
351 {
352 CopyToModeClipboard(selectedText, QClipboard::Clipboard);
353 }
354
PasteFromMode(QClipboard::Mode clipboardMode_)355 void ScintillaQt::PasteFromMode(QClipboard::Mode clipboardMode_)
356 {
357 QClipboard *clipboard = QApplication::clipboard();
358 const QMimeData *mimeData = clipboard->mimeData(clipboardMode_);
359 bool isRectangular = IsRectangularInMime(mimeData);
360 bool isLine = SelectionEmpty() && IsLineCutCopyInMime(mimeData);
361 QString text = clipboard->text(clipboardMode_);
362 QByteArray utext = BytesForDocument(text);
363 std::string dest(utext.constData(), utext.length());
364 SelectionText selText;
365 selText.Copy(dest, pdoc->dbcsCodePage, CharacterSetOfDocument(), isRectangular, false);
366
367 UndoGroup ug(pdoc);
368 ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH);
369 InsertPasteShape(selText.Data(), selText.Length(),
370 isRectangular ? pasteRectangular : (isLine ? pasteLine : pasteStream));
371 EnsureCaretVisible();
372 }
373
Paste()374 void ScintillaQt::Paste()
375 {
376 PasteFromMode(QClipboard::Clipboard);
377 }
378
ClaimSelection()379 void ScintillaQt::ClaimSelection()
380 {
381 if (QApplication::clipboard()->supportsSelection()) {
382 // X Windows has a 'primary selection' as well as the clipboard.
383 // Whenever the user selects some text, we become the primary selection
384 if (!sel.Empty()) {
385 primarySelection = true;
386 SelectionText st;
387 CopySelectionRange(&st);
388 CopyToModeClipboard(st, QClipboard::Selection);
389 } else {
390 primarySelection = false;
391 }
392 }
393 }
394
NotifyChange()395 void ScintillaQt::NotifyChange()
396 {
397 emit notifyChange();
398 emit command(
399 Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE),
400 reinterpret_cast<sptr_t>(wMain.GetID()));
401 }
402
NotifyFocus(bool focus)403 void ScintillaQt::NotifyFocus(bool focus)
404 {
405 if (commandEvents) {
406 emit command(
407 Platform::LongFromTwoShorts
408 (GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS),
409 reinterpret_cast<sptr_t>(wMain.GetID()));
410 }
411
412 Editor::NotifyFocus(focus);
413 }
414
NotifyParent(SCNotification scn)415 void ScintillaQt::NotifyParent(SCNotification scn)
416 {
417 scn.nmhdr.hwndFrom = wMain.GetID();
418 scn.nmhdr.idFrom = GetCtrlID();
419 emit notifyParent(scn);
420 }
421
NotifyURIDropped(const char * uri)422 void ScintillaQt::NotifyURIDropped(const char *uri)
423 {
424 SCNotification scn = {};
425 scn.nmhdr.code = SCN_URIDROPPED;
426 scn.text = uri;
427
428 NotifyParent(scn);
429 }
430
FineTickerRunning(TickReason reason)431 bool ScintillaQt::FineTickerRunning(TickReason reason)
432 {
433 return timers[reason] != 0;
434 }
435
FineTickerStart(TickReason reason,int millis,int)436 void ScintillaQt::FineTickerStart(TickReason reason, int millis, int /* tolerance */)
437 {
438 FineTickerCancel(reason);
439 timers[reason] = startTimer(millis);
440 }
441
442 // CancelTimers cleans up all fine-ticker timers and is non-virtual to avoid warnings when
443 // called during destruction.
CancelTimers()444 void ScintillaQt::CancelTimers()
445 {
446 for (TickReason tr = tickCaret; tr <= tickDwell; tr = static_cast<TickReason>(tr + 1)) {
447 if (timers[tr]) {
448 killTimer(timers[tr]);
449 timers[tr] = 0;
450 }
451 }
452 }
453
FineTickerCancel(TickReason reason)454 void ScintillaQt::FineTickerCancel(TickReason reason)
455 {
456 if (timers[reason]) {
457 killTimer(timers[reason]);
458 timers[reason] = 0;
459 }
460 }
461
onIdle()462 void ScintillaQt::onIdle()
463 {
464 bool continueIdling = Idle();
465 if (!continueIdling) {
466 SetIdle(false);
467 }
468 }
469
ChangeIdle(bool on)470 bool ScintillaQt::ChangeIdle(bool on)
471 {
472 QTimer *qIdle;
473 if (on) {
474 // Start idler, if it's not running.
475 if (!idler.state) {
476 idler.state = true;
477 qIdle = new QTimer;
478 connect(qIdle, SIGNAL(timeout()), this, SLOT(onIdle()));
479 qIdle->start(0);
480 idler.idlerID = qIdle;
481 }
482 } else {
483 // Stop idler, if it's running
484 if (idler.state) {
485 idler.state = false;
486 qIdle = static_cast<QTimer *>(idler.idlerID);
487 qIdle->stop();
488 disconnect(qIdle, SIGNAL(timeout()), nullptr, nullptr);
489 delete qIdle;
490 idler.idlerID = {};
491 }
492 }
493 return true;
494 }
495
SetIdle(bool on)496 bool ScintillaQt::SetIdle(bool on)
497 {
498 return ChangeIdle(on);
499 }
500
CharacterSetOfDocument() const501 int ScintillaQt::CharacterSetOfDocument() const
502 {
503 return vs.styles[STYLE_DEFAULT].characterSet;
504 }
505
CharacterSetIDOfDocument() const506 const char *ScintillaQt::CharacterSetIDOfDocument() const
507 {
508 return CharacterSetID(CharacterSetOfDocument());
509 }
510
StringFromDocument(const char * s) const511 QString ScintillaQt::StringFromDocument(const char *s) const
512 {
513 if (IsUnicodeMode()) {
514 return QString::fromUtf8(s);
515 } else {
516 QTextCodec *codec = QTextCodec::codecForName(
517 CharacterSetID(CharacterSetOfDocument()));
518 return codec->toUnicode(s);
519 }
520 }
521
BytesForDocument(const QString & text) const522 QByteArray ScintillaQt::BytesForDocument(const QString &text) const
523 {
524 if (IsUnicodeMode()) {
525 return text.toUtf8();
526 } else {
527 QTextCodec *codec = QTextCodec::codecForName(
528 CharacterSetID(CharacterSetOfDocument()));
529 return codec->fromUnicode(text);
530 }
531 }
532
533
534 class CaseFolderDBCS : public CaseFolderTable {
535 QTextCodec *codec;
536 public:
CaseFolderDBCS(QTextCodec * codec_)537 explicit CaseFolderDBCS(QTextCodec *codec_) : codec(codec_) {
538 StandardASCII();
539 }
Fold(char * folded,size_t sizeFolded,const char * mixed,size_t lenMixed)540 size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override {
541 if ((lenMixed == 1) && (sizeFolded > 0)) {
542 folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
543 return 1;
544 } else if (codec) {
545 QString su = codec->toUnicode(mixed, static_cast<int>(lenMixed));
546 QString suFolded = su.toCaseFolded();
547 QByteArray bytesFolded = codec->fromUnicode(suFolded);
548
549 if (bytesFolded.length() < static_cast<int>(sizeFolded)) {
550 memcpy(folded, bytesFolded, bytesFolded.length());
551 return bytesFolded.length();
552 }
553 }
554 // Something failed so return a single NUL byte
555 folded[0] = '\0';
556 return 1;
557 }
558 };
559
CaseFolderForEncoding()560 CaseFolder *ScintillaQt::CaseFolderForEncoding()
561 {
562 if (pdoc->dbcsCodePage == SC_CP_UTF8) {
563 return new CaseFolderUnicode();
564 } else {
565 const char *charSetBuffer = CharacterSetIDOfDocument();
566 if (charSetBuffer) {
567 if (pdoc->dbcsCodePage == 0) {
568 CaseFolderTable *pcf = new CaseFolderTable();
569 pcf->StandardASCII();
570 QTextCodec *codec = QTextCodec::codecForName(charSetBuffer);
571 // Only for single byte encodings
572 for (int i=0x80; i<0x100; i++) {
573 char sCharacter[2] = "A";
574 sCharacter[0] = static_cast<char>(i);
575 QString su = codec->toUnicode(sCharacter, 1);
576 QString suFolded = su.toCaseFolded();
577 if (codec->canEncode(suFolded)) {
578 QByteArray bytesFolded = codec->fromUnicode(suFolded);
579 if (bytesFolded.length() == 1) {
580 pcf->SetTranslation(sCharacter[0], bytesFolded[0]);
581 }
582 }
583 }
584 return pcf;
585 } else {
586 return new CaseFolderDBCS(QTextCodec::codecForName(charSetBuffer));
587 }
588 }
589 return nullptr;
590 }
591 }
592
CaseMapString(const std::string & s,int caseMapping)593 std::string ScintillaQt::CaseMapString(const std::string &s, int caseMapping)
594 {
595 if ((s.size() == 0) || (caseMapping == cmSame))
596 return s;
597
598 if (IsUnicodeMode()) {
599 std::string retMapped(s.length() * maxExpansionCaseConversion, 0);
600 size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(),
601 (caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower);
602 retMapped.resize(lenMapped);
603 return retMapped;
604 }
605
606 QTextCodec *codec = QTextCodec::codecForName(CharacterSetIDOfDocument());
607 QString text = codec->toUnicode(s.c_str(), static_cast<int>(s.length()));
608
609 if (caseMapping == cmUpper) {
610 text = text.toUpper();
611 } else {
612 text = text.toLower();
613 }
614
615 QByteArray bytes = BytesForDocument(text);
616 return std::string(bytes.data(), bytes.length());
617 }
618
SetMouseCapture(bool on)619 void ScintillaQt::SetMouseCapture(bool on)
620 {
621 // This is handled automatically by Qt
622 if (mouseDownCaptures) {
623 haveMouseCapture = on;
624 }
625 }
626
HaveMouseCapture()627 bool ScintillaQt::HaveMouseCapture()
628 {
629 return haveMouseCapture;
630 }
631
StartDrag()632 void ScintillaQt::StartDrag()
633 {
634 inDragDrop = ddDragging;
635 dropWentOutside = true;
636 if (drag.Length()) {
637 QMimeData *mimeData = new QMimeData;
638 QString sText = StringFromSelectedText(drag);
639 mimeData->setText(sText);
640 if (drag.rectangular) {
641 AddRectangularToMime(mimeData, sText);
642 }
643 // This QDrag is not freed as that causes a crash on Linux
644 QDrag *dragon = new QDrag(scrollArea);
645 dragon->setMimeData(mimeData);
646
647 Qt::DropAction dropAction = dragon->exec(Qt::CopyAction|Qt::MoveAction);
648 if ((dropAction == Qt::MoveAction) && dropWentOutside) {
649 // Remove dragged out text
650 ClearSelection();
651 }
652 }
653 inDragDrop = ddNone;
654 SetDragPosition(SelectionPosition(Sci::invalidPosition));
655 }
656
657 class CallTipImpl : public QWidget {
658 public:
CallTipImpl(CallTip * pct_)659 CallTipImpl(CallTip *pct_)
660 : QWidget(nullptr, Qt::ToolTip),
661 pct(pct_)
662 {
663 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
664 setWindowFlag(Qt::WindowTransparentForInput);
665 #endif
666 }
667
paintEvent(QPaintEvent *)668 void paintEvent(QPaintEvent *) override
669 {
670 if (pct->inCallTipMode) {
671 Surface *surfaceWindow = Surface::Allocate(0);
672 surfaceWindow->Init(this);
673 surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == pct->codePage);
674 surfaceWindow->SetDBCSMode(pct->codePage);
675 pct->PaintCT(surfaceWindow);
676 delete surfaceWindow;
677 }
678 }
679
680 private:
681 CallTip *pct;
682 };
683
CreateCallTipWindow(PRectangle rc)684 void ScintillaQt::CreateCallTipWindow(PRectangle rc)
685 {
686
687 if (!ct.wCallTip.Created()) {
688 QWidget *pCallTip = new CallTipImpl(&ct);
689 ct.wCallTip = pCallTip;
690 pCallTip->move(rc.left, rc.top);
691 pCallTip->resize(rc.Width(), rc.Height());
692 }
693 }
694
AddToPopUp(const char * label,int cmd,bool enabled)695 void ScintillaQt::AddToPopUp(const char *label,
696 int cmd,
697 bool enabled)
698 {
699 QMenu *menu = static_cast<QMenu *>(popup.GetID());
700 QString text(label);
701
702 if (text.isEmpty()) {
703 menu->addSeparator();
704 } else {
705 QAction *action = menu->addAction(text);
706 action->setData(cmd);
707 action->setEnabled(enabled);
708 }
709
710 // Make sure the menu's signal is connected only once.
711 menu->disconnect();
712 connect(menu, SIGNAL(triggered(QAction *)),
713 this, SLOT(execCommand(QAction *)));
714 }
715
WndProc(unsigned int iMessage,uptr_t wParam,sptr_t lParam)716 sptr_t ScintillaQt::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam)
717 {
718 try {
719 switch (iMessage) {
720
721 case SCI_SETIMEINTERACTION:
722 // Only inline IME supported on Qt
723 break;
724
725 case SCI_GRABFOCUS:
726 scrollArea->setFocus(Qt::OtherFocusReason);
727 break;
728
729 case SCI_GETDIRECTFUNCTION:
730 return reinterpret_cast<sptr_t>(DirectFunction);
731
732 case SCI_GETDIRECTPOINTER:
733 return reinterpret_cast<sptr_t>(this);
734
735 default:
736 return ScintillaBase::WndProc(iMessage, wParam, lParam);
737 }
738 } catch (std::bad_alloc &) {
739 errorStatus = SC_STATUS_BADALLOC;
740 } catch (...) {
741 errorStatus = SC_STATUS_FAILURE;
742 }
743 return 0;
744 }
745
DefWndProc(unsigned int,uptr_t,sptr_t)746 sptr_t ScintillaQt::DefWndProc(unsigned int, uptr_t, sptr_t)
747 {
748 return 0;
749 }
750
DirectFunction(sptr_t ptr,unsigned int iMessage,uptr_t wParam,sptr_t lParam)751 sptr_t ScintillaQt::DirectFunction(
752 sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
753 {
754 return reinterpret_cast<ScintillaQt *>(ptr)->WndProc(iMessage, wParam, lParam);
755 }
756
757 // Additions to merge in Scientific Toolworks widget structure
758
PartialPaint(const PRectangle & rect)759 void ScintillaQt::PartialPaint(const PRectangle &rect)
760 {
761 rcPaint = rect;
762 paintState = painting;
763 PRectangle rcClient = GetClientRectangle();
764 paintingAllText = rcPaint.Contains(rcClient);
765
766 AutoSurface surfacePaint(this);
767 Paint(surfacePaint, rcPaint);
768 surfacePaint->Release();
769
770 if (paintState == paintAbandoned) {
771 // FIXME: Failure to paint the requested rectangle in each
772 // paint event causes flicker on some platforms (Mac?)
773 // Paint rect immediately.
774 paintState = painting;
775 paintingAllText = true;
776
777 AutoSurface surface(this);
778 Paint(surface, rcPaint);
779 surface->Release();
780
781 // Queue a full repaint.
782 scrollArea->viewport()->update();
783 }
784
785 paintState = notPainting;
786 }
787
DragEnter(const Point & point)788 void ScintillaQt::DragEnter(const Point &point)
789 {
790 SetDragPosition(SPositionFromLocation(point,
791 false, false, UserVirtualSpace()));
792 }
793
DragMove(const Point & point)794 void ScintillaQt::DragMove(const Point &point)
795 {
796 SetDragPosition(SPositionFromLocation(point,
797 false, false, UserVirtualSpace()));
798 }
799
DragLeave()800 void ScintillaQt::DragLeave()
801 {
802 SetDragPosition(SelectionPosition(Sci::invalidPosition));
803 }
804
Drop(const Point & point,const QMimeData * data,bool move)805 void ScintillaQt::Drop(const Point &point, const QMimeData *data, bool move)
806 {
807 QString text = data->text();
808 bool rectangular = IsRectangularInMime(data);
809 QByteArray bytes = BytesForDocument(text);
810 int len = bytes.length();
811
812 SelectionPosition movePos = SPositionFromLocation(point,
813 false, false, UserVirtualSpace());
814
815 DropAt(movePos, bytes, len, move, rectangular);
816 }
817
DropUrls(const QMimeData * data)818 void ScintillaQt::DropUrls(const QMimeData *data)
819 {
820 foreach(const QUrl &url, data->urls()) {
821 NotifyURIDropped(url.toString().toUtf8().constData());
822 }
823 }
824
timerEvent(QTimerEvent * event)825 void ScintillaQt::timerEvent(QTimerEvent *event)
826 {
827 for (TickReason tr=tickCaret; tr<=tickDwell; tr = static_cast<TickReason>(tr+1)) {
828 if (timers[tr] == event->timerId()) {
829 TickFor(tr);
830 }
831 }
832 }
833