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 | |
25 | using namespace Scintilla; |
26 | using namespace Scintilla::Internal; |
27 | |
28 | ScintillaQt::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 | |
48 | ScintillaQt::~ScintillaQt() |
49 | { |
50 | CancelTimers(); |
51 | ChangeIdle(false); |
52 | } |
53 | |
54 | void ScintillaQt::execCommand(QAction *action) |
55 | { |
56 | int command = action->data().toInt(); |
57 | Command(command); |
58 | } |
59 | |
60 | #if defined(Q_OS_WIN) |
61 | static const QString sMSDEVColumnSelect("MSDEVColumnSelect" ); |
62 | static const QString sWrappedMSDEVColumnSelect("application/x-qt-windows-mime;value=\"MSDEVColumnSelect\"" ); |
63 | static const QString sVSEditorLineCutCopy("VisualStudioEditorOperationsLineCutCopyClipboardTag" ); |
64 | static const QString sWrappedVSEditorLineCutCopy("application/x-qt-windows-mime;value=\"VisualStudioEditorOperationsLineCutCopyClipboardTag\"" ); |
65 | #elif defined(Q_OS_MAC) |
66 | static const QString sScintillaRecPboardType("com.scintilla.utf16-plain-text.rectangular" ); |
67 | static const QString sScintillaRecMimeType("text/x-scintilla.utf16-plain-text.rectangular" ); |
68 | #else |
69 | // Linux |
70 | static 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 | |
75 | class ScintillaRectangularMime : public QMacPasteboardMime { |
76 | public: |
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. |
120 | static ScintillaRectangularMime *singletonMime = 0; |
121 | |
122 | #endif |
123 | |
124 | void 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 | |
141 | void ScintillaQt::Finalise() |
142 | { |
143 | CancelTimers(); |
144 | ScintillaBase::Finalise(); |
145 | } |
146 | |
147 | void ScintillaQt::SelectionChanged() |
148 | { |
149 | bool nowPrimary = QApplication::clipboard()->ownsSelection(); |
150 | if (nowPrimary != primarySelection) { |
151 | primarySelection = nowPrimary; |
152 | Redraw(); |
153 | } |
154 | } |
155 | |
156 | bool 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 | |
164 | static 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 | |
175 | static 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 | |
192 | static 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 | |
200 | static 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 | |
224 | static 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 | |
241 | bool 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 | |
252 | std::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 | |
263 | std::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 | |
275 | void ScintillaQt::ScrollText(Sci::Line linesToMove) |
276 | { |
277 | int dy = vs.lineHeight * (linesToMove); |
278 | scrollArea->viewport()->scroll(0, dy); |
279 | } |
280 | |
281 | void ScintillaQt::SetVerticalScrollPos() |
282 | { |
283 | scrollArea->verticalScrollBar()->setValue(topLine); |
284 | emit verticalScrolled(topLine); |
285 | } |
286 | |
287 | void ScintillaQt::SetHorizontalScrollPos() |
288 | { |
289 | scrollArea->horizontalScrollBar()->setValue(xOffset); |
290 | emit horizontalScrolled(xOffset); |
291 | } |
292 | |
293 | bool 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 | |
327 | void 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 | |
342 | void 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 | |
362 | void ScintillaQt::Copy() |
363 | { |
364 | if (!sel.Empty()) { |
365 | SelectionText st; |
366 | CopySelectionRange(&st); |
367 | CopyToClipboard(st); |
368 | } |
369 | } |
370 | |
371 | void ScintillaQt::CopyToClipboard(const SelectionText &selectedText) |
372 | { |
373 | CopyToModeClipboard(selectedText, QClipboard::Clipboard); |
374 | } |
375 | |
376 | void 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 | |
395 | void ScintillaQt::Paste() |
396 | { |
397 | PasteFromMode(QClipboard::Clipboard); |
398 | } |
399 | |
400 | void 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 | |
416 | void ScintillaQt::NotifyChange() |
417 | { |
418 | emit notifyChange(); |
419 | emit command( |
420 | Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE), |
421 | reinterpret_cast<sptr_t>(wMain.GetID())); |
422 | } |
423 | |
424 | void 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 | |
436 | void ScintillaQt::NotifyParent(NotificationData scn) |
437 | { |
438 | scn.nmhdr.hwndFrom = wMain.GetID(); |
439 | scn.nmhdr.idFrom = GetCtrlID(); |
440 | emit notifyParent(scn); |
441 | } |
442 | |
443 | void 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 | |
452 | bool ScintillaQt::FineTickerRunning(TickReason reason) |
453 | { |
454 | return timers[static_cast<size_t>(reason)] != 0; |
455 | } |
456 | |
457 | void 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. |
465 | void 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 | |
475 | void 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 | |
484 | void ScintillaQt::onIdle() |
485 | { |
486 | bool continueIdling = Idle(); |
487 | if (!continueIdling) { |
488 | SetIdle(false); |
489 | } |
490 | } |
491 | |
492 | bool 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 | |
517 | bool ScintillaQt::SetIdle(bool on) |
518 | { |
519 | return ChangeIdle(on); |
520 | } |
521 | |
522 | CharacterSet ScintillaQt::CharacterSetOfDocument() const |
523 | { |
524 | return vs.styles[STYLE_DEFAULT].characterSet; |
525 | } |
526 | |
527 | const char *ScintillaQt::CharacterSetIDOfDocument() const |
528 | { |
529 | return CharacterSetID(CharacterSetOfDocument()); |
530 | } |
531 | |
532 | QString 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 | |
543 | QByteArray 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 | |
555 | class CaseFolderDBCS : public CaseFolderTable { |
556 | QTextCodec *codec; |
557 | public: |
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 | |
581 | std::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 | |
614 | std::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 | |
640 | void ScintillaQt::SetMouseCapture(bool on) |
641 | { |
642 | // This is handled automatically by Qt |
643 | if (mouseDownCaptures) { |
644 | haveMouseCapture = on; |
645 | } |
646 | } |
647 | |
648 | bool ScintillaQt::HaveMouseCapture() |
649 | { |
650 | return haveMouseCapture; |
651 | } |
652 | |
653 | void 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 | |
678 | class CallTipImpl : public QWidget { |
679 | public: |
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 | |
699 | private: |
700 | CallTip *pct; |
701 | }; |
702 | |
703 | void 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 | |
714 | void ScintillaQt::(const char *label, |
715 | int cmd, |
716 | bool enabled) |
717 | { |
718 | QMenu * = 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 | |
735 | sptr_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 | |
768 | sptr_t ScintillaQt::DefWndProc(Message, uptr_t, sptr_t) |
769 | { |
770 | return 0; |
771 | } |
772 | |
773 | sptr_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 | |
780 | sptr_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 | |
791 | void 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 | |
820 | void ScintillaQt::DragEnter(const Point &point) |
821 | { |
822 | SetDragPosition(SPositionFromLocation(point, |
823 | false, false, UserVirtualSpace())); |
824 | } |
825 | |
826 | void ScintillaQt::DragMove(const Point &point) |
827 | { |
828 | SetDragPosition(SPositionFromLocation(point, |
829 | false, false, UserVirtualSpace())); |
830 | } |
831 | |
832 | void ScintillaQt::DragLeave() |
833 | { |
834 | SetDragPosition(SelectionPosition(Sci::invalidPosition)); |
835 | } |
836 | |
837 | void 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 | |
850 | void ScintillaQt::DropUrls(const QMimeData *data) |
851 | { |
852 | foreach(const QUrl &url, data->urls()) { |
853 | NotifyURIDropped(url.toString().toUtf8().constData()); |
854 | } |
855 | } |
856 | |
857 | void 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 | |