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