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