// The implementation of the Qt specific subclass of ScintillaBase. // // Copyright (c) 2017 Riverbank Computing Limited // // This file is part of QScintilla. // // This file may be used under the terms of the GNU General Public License // version 3.0 as published by the Free Software Foundation and appearing in // the file LICENSE included in the packaging of this file. Please review the // following information to ensure the GNU General Public License version 3.0 // requirements will be met: http://www.gnu.org/copyleft/gpl.html. // // If you do not wish to use this file under the terms of the GPL version 3.0 // then you may purchase a commercial license. For more information contact // info@riverbankcomputing.com. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. #include #include #include #include #include #include #include #include #include #include "Qsci/qsciscintillabase.h" #include "ScintillaQt.h" #include "SciClasses.h" // We want to use the Scintilla notification names as Qt signal names. #undef SCEN_CHANGE #undef SCN_AUTOCCANCELLED #undef SCN_AUTOCCHARDELETED #undef SCN_AUTOCCOMPLETED #undef SCN_AUTOCSELECTION #undef SCN_CALLTIPCLICK #undef SCN_CHARADDED #undef SCN_DOUBLECLICK #undef SCN_DWELLEND #undef SCN_DWELLSTART #undef SCN_FOCUSIN #undef SCN_FOCUSOUT #undef SCN_HOTSPOTCLICK #undef SCN_HOTSPOTDOUBLECLICK #undef SCN_HOTSPOTRELEASECLICK #undef SCN_INDICATORCLICK #undef SCN_INDICATORRELEASE #undef SCN_MACRORECORD #undef SCN_MARGINCLICK #undef SCN_MARGINRIGHTCLICK #undef SCN_MODIFIED #undef SCN_MODIFYATTEMPTRO #undef SCN_NEEDSHOWN #undef SCN_PAINTED #undef SCN_SAVEPOINTLEFT #undef SCN_SAVEPOINTREACHED #undef SCN_STYLENEEDED #undef SCN_UPDATEUI #undef SCN_USERLISTSELECTION #undef SCN_ZOOM enum { SCEN_CHANGE = 768, SCN_AUTOCCANCELLED = 2025, SCN_AUTOCCHARDELETED = 2026, SCN_AUTOCCOMPLETED = 2030, SCN_AUTOCSELECTION = 2022, SCN_CALLTIPCLICK = 2021, SCN_CHARADDED = 2001, SCN_DOUBLECLICK = 2006, SCN_DWELLEND = 2017, SCN_DWELLSTART = 2016, SCN_FOCUSIN = 2028, SCN_FOCUSOUT = 2029, SCN_HOTSPOTCLICK = 2019, SCN_HOTSPOTDOUBLECLICK = 2020, SCN_HOTSPOTRELEASECLICK = 2027, SCN_INDICATORCLICK = 2023, SCN_INDICATORRELEASE = 2024, SCN_MACRORECORD = 2009, SCN_MARGINCLICK = 2010, SCN_MARGINRIGHTCLICK = 2031, SCN_MODIFIED = 2008, SCN_MODIFYATTEMPTRO = 2004, SCN_NEEDSHOWN = 2011, SCN_PAINTED = 2013, SCN_SAVEPOINTLEFT = 2003, SCN_SAVEPOINTREACHED = 2002, SCN_STYLENEEDED = 2000, SCN_UPDATEUI = 2007, SCN_USERLISTSELECTION = 2014, SCN_ZOOM = 2018 }; // The ctor. QsciScintillaQt::QsciScintillaQt(QsciScintillaBase *qsb_) : vMax(0), hMax(0), vPage(0), hPage(0), capturedMouse(false), qsb(qsb_) { wMain = qsb->viewport(); // This is ignored. imeInteraction = imeInline; for (int i = 0; i <= static_cast(tickPlatform); ++i) timers[i] = 0; Initialise(); } // The dtor. QsciScintillaQt::~QsciScintillaQt() { Finalise(); } // Initialise the instance. void QsciScintillaQt::Initialise() { } // Tidy up the instance. void QsciScintillaQt::Finalise() { for (int i = 0; i <= static_cast(tickPlatform); ++i) FineTickerCancel(static_cast(i)); ScintillaBase::Finalise(); } // Start a drag. void QsciScintillaQt::StartDrag() { inDragDrop = ddDragging; QDrag *qdrag = new QDrag(qsb); qdrag->setMimeData(mimeSelection(drag)); #if QT_VERSION >= 0x040300 Qt::DropAction action = qdrag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction); #else Qt::DropAction action = qdrag->start(Qt::MoveAction); #endif // Remove the dragged text if it was a move to another widget or // application. if (action == Qt::MoveAction && qdrag->target() != qsb->viewport()) ClearSelection(); SetDragPosition(QSCI_SCI_NAMESPACE(SelectionPosition)()); inDragDrop = ddNone; } // Re-implement to trap certain messages. sptr_t QsciScintillaQt::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { switch (iMessage) { case SCI_GETDIRECTFUNCTION: return reinterpret_cast(DirectFunction); case SCI_GETDIRECTPOINTER: return reinterpret_cast(this); } return ScintillaBase::WndProc(iMessage, wParam, lParam); } // Windows nonsense. sptr_t QsciScintillaQt::DefWndProc(unsigned int, uptr_t, sptr_t) { return 0; } // Grab or release the mouse (and keyboard). void QsciScintillaQt::SetMouseCapture(bool on) { if (mouseDownCaptures) if (on) qsb->viewport()->grabMouse(); else qsb->viewport()->releaseMouse(); capturedMouse = on; } // Return true if the mouse/keyboard are currently grabbed. bool QsciScintillaQt::HaveMouseCapture() { return capturedMouse; } // Set the position of the vertical scrollbar. void QsciScintillaQt::SetVerticalScrollPos() { QScrollBar *sb = qsb->verticalScrollBar(); bool was_blocked = sb->blockSignals(true); sb->setValue(topLine); sb->blockSignals(was_blocked); } // Set the position of the horizontal scrollbar. void QsciScintillaQt::SetHorizontalScrollPos() { QScrollBar *sb = qsb->horizontalScrollBar(); bool was_blocked = sb->blockSignals(true); sb->setValue(xOffset); sb->blockSignals(was_blocked); } // Set the extent of the vertical and horizontal scrollbars and return true if // the view needs re-drawing. bool QsciScintillaQt::ModifyScrollBars(int nMax,int nPage) { bool modified = false; QScrollBar *sb; int vNewPage = nPage; int vNewMax = nMax - vNewPage + 1; if (vMax != vNewMax || vPage != vNewPage) { vMax = vNewMax; vPage = vNewPage; modified = true; sb = qsb->verticalScrollBar(); sb->setMaximum(vMax); sb->setPageStep(vPage); } int hNewPage = GetTextRectangle().Width(); int hNewMax = (scrollWidth > hNewPage) ? scrollWidth - hNewPage : 0; int charWidth = vs.styles[STYLE_DEFAULT].aveCharWidth; sb = qsb->horizontalScrollBar(); if (hMax != hNewMax || hPage != hNewPage || sb->singleStep() != charWidth) { hMax = hNewMax; hPage = hNewPage; modified = true; sb->setMaximum(hMax); sb->setPageStep(hPage); sb->setSingleStep(charWidth); } return modified; } // Called after SCI_SETWRAPMODE and SCI_SETHSCROLLBAR. void QsciScintillaQt::ReconfigureScrollBars() { // Hide or show the scrollbars if needed. bool hsb = (horizontalScrollBarVisible && !Wrapping()); qsb->setHorizontalScrollBarPolicy(hsb ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff); qsb->setVerticalScrollBarPolicy(verticalScrollBarVisible ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff); } // Notify interested parties of any change in the document. void QsciScintillaQt::NotifyChange() { emit qsb->SCEN_CHANGE(); } // Notify interested parties of various events. This is the main mapping // between Scintilla notifications and Qt signals. void QsciScintillaQt::NotifyParent(SCNotification scn) { switch (scn.nmhdr.code) { case SCN_CALLTIPCLICK: emit qsb->SCN_CALLTIPCLICK(scn.position); break; case SCN_AUTOCCANCELLED: emit qsb->SCN_AUTOCCANCELLED(); break; case SCN_AUTOCCHARDELETED: emit qsb->SCN_AUTOCCHARDELETED(); break; case SCN_AUTOCCOMPLETED: emit qsb->SCN_AUTOCCOMPLETED(scn.text, scn.lParam, scn.ch, scn.listCompletionMethod); break; case SCN_AUTOCSELECTION: emit qsb->SCN_AUTOCSELECTION(scn.text, scn.lParam, scn.ch, scn.listCompletionMethod); emit qsb->SCN_AUTOCSELECTION(scn.text, scn.lParam); break; case SCN_CHARADDED: emit qsb->SCN_CHARADDED(scn.ch); break; case SCN_DOUBLECLICK: emit qsb->SCN_DOUBLECLICK(scn.position, scn.line, scn.modifiers); break; case SCN_DWELLEND: emit qsb->SCN_DWELLEND(scn.position, scn.x, scn.y); break; case SCN_DWELLSTART: emit qsb->SCN_DWELLSTART(scn.position, scn.x, scn.y); break; case SCN_FOCUSIN: emit qsb->SCN_FOCUSIN(); break; case SCN_FOCUSOUT: emit qsb->SCN_FOCUSOUT(); break; case SCN_HOTSPOTCLICK: emit qsb->SCN_HOTSPOTCLICK(scn.position, scn.modifiers); break; case SCN_HOTSPOTDOUBLECLICK: emit qsb->SCN_HOTSPOTDOUBLECLICK(scn.position, scn.modifiers); break; case SCN_HOTSPOTRELEASECLICK: emit qsb->SCN_HOTSPOTRELEASECLICK(scn.position, scn.modifiers); break; case SCN_INDICATORCLICK: emit qsb->SCN_INDICATORCLICK(scn.position, scn.modifiers); break; case SCN_INDICATORRELEASE: emit qsb->SCN_INDICATORRELEASE(scn.position, scn.modifiers); break; case SCN_MACRORECORD: emit qsb->SCN_MACRORECORD(scn.message, scn.wParam, reinterpret_cast(scn.lParam)); break; case SCN_MARGINCLICK: emit qsb->SCN_MARGINCLICK(scn.position, scn.modifiers, scn.margin); break; case SCN_MARGINRIGHTCLICK: emit qsb->SCN_MARGINRIGHTCLICK(scn.position, scn.modifiers, scn.margin); break; case SCN_MODIFIED: { char *text; // Give some protection to the Python bindings. if (scn.text && (scn.modificationType & (SC_MOD_INSERTTEXT|SC_MOD_DELETETEXT) != 0)) { text = new char[scn.length + 1]; memcpy(text, scn.text, scn.length); text[scn.length] = '\0'; } else { text = 0; } emit qsb->SCN_MODIFIED(scn.position, scn.modificationType, text, scn.length, scn.linesAdded, scn.line, scn.foldLevelNow, scn.foldLevelPrev, scn.token, scn.annotationLinesAdded); if (text) delete[] text; break; } case SCN_MODIFYATTEMPTRO: emit qsb->SCN_MODIFYATTEMPTRO(); break; case SCN_NEEDSHOWN: emit qsb->SCN_NEEDSHOWN(scn.position, scn.length); break; case SCN_PAINTED: emit qsb->SCN_PAINTED(); break; case SCN_SAVEPOINTLEFT: emit qsb->SCN_SAVEPOINTLEFT(); break; case SCN_SAVEPOINTREACHED: emit qsb->SCN_SAVEPOINTREACHED(); break; case SCN_STYLENEEDED: emit qsb->SCN_STYLENEEDED(scn.position); break; case SCN_UPDATEUI: emit qsb->SCN_UPDATEUI(scn.updated); break; case SCN_USERLISTSELECTION: emit qsb->SCN_USERLISTSELECTION(scn.text, scn.wParam, scn.ch, scn.listCompletionMethod); emit qsb->SCN_USERLISTSELECTION(scn.text, scn.wParam); break; case SCN_ZOOM: emit qsb->SCN_ZOOM(); break; default: qWarning("Unknown notification: %u", scn.nmhdr.code); } } // Convert a selection to mime data. QMimeData *QsciScintillaQt::mimeSelection( const QSCI_SCI_NAMESPACE(SelectionText) &text) const { return qsb->toMimeData(QByteArray(text.Data()), text.rectangular); } // Copy the selected text to the clipboard. void QsciScintillaQt::CopyToClipboard( const QSCI_SCI_NAMESPACE(SelectionText) &selectedText) { QApplication::clipboard()->setMimeData(mimeSelection(selectedText)); } // Implement copy. void QsciScintillaQt::Copy() { if (!sel.Empty()) { QSCI_SCI_NAMESPACE(SelectionText) text; CopySelectionRange(&text); CopyToClipboard(text); } } // Implement pasting text. void QsciScintillaQt::Paste() { pasteFromClipboard(QClipboard::Clipboard); } // Paste text from either the clipboard or selection. void QsciScintillaQt::pasteFromClipboard(QClipboard::Mode mode) { int len; const char *s; bool rectangular; const QMimeData *source = QApplication::clipboard()->mimeData(mode); if (!source || !qsb->canInsertFromMimeData(source)) return; QByteArray text = qsb->fromMimeData(source, rectangular); len = text.length(); s = text.data(); std::string dest = QSCI_SCI_NAMESPACE(Document)::TransformLineEnds(s, len, pdoc->eolMode); QSCI_SCI_NAMESPACE(SelectionText) selText; selText.Copy(dest, (IsUnicodeMode() ? SC_CP_UTF8 : 0), vs.styles[STYLE_DEFAULT].characterSet, rectangular, false); QSCI_SCI_NAMESPACE(UndoGroup) ug(pdoc); ClearSelection(); InsertPasteShape(selText.Data(), selText.Length(), selText.rectangular ? pasteRectangular : pasteStream); EnsureCaretVisible(); } // Create a call tip window. void QsciScintillaQt::CreateCallTipWindow(QSCI_SCI_NAMESPACE(PRectangle) rc) { if (!ct.wCallTip.Created()) ct.wCallTip = ct.wDraw = new QsciSciCallTip(qsb, this); QsciSciCallTip *w = reinterpret_cast(ct.wCallTip.GetID()); w->resize(rc.right - rc.left, rc.bottom - rc.top); ct.wCallTip.Show(); } // Add an item to the right button menu. void QsciScintillaQt::AddToPopUp(const char *label, int cmd, bool enabled) { QsciSciPopup *pm = static_cast(popup.GetID()); if (*label) pm->addItem(qApp->translate("ContextMenu", label), cmd, enabled, this); else pm->addSeparator(); } // Claim the selection. void QsciScintillaQt::ClaimSelection() { bool isSel = !sel.Empty(); if (isSel) { QClipboard *cb = QApplication::clipboard(); // If we support X11 style selection then make it available now. if (cb->supportsSelection()) { QSCI_SCI_NAMESPACE(SelectionText) text; CopySelectionRange(&text); if (text.Data()) cb->setMimeData(mimeSelection(text), QClipboard::Selection); } primarySelection = true; } else primarySelection = false; emit qsb->QSCN_SELCHANGED(isSel); } // Unclaim the selection. void QsciScintillaQt::UnclaimSelection() { if (primarySelection) { primarySelection = false; qsb->viewport()->update(); } } // Implemented to provide compatibility with the Windows version. sptr_t QsciScintillaQt::DirectFunction(QsciScintillaQt *sciThis, unsigned int iMessage, uptr_t wParam, sptr_t lParam) { return sciThis->WndProc(iMessage,wParam,lParam); } // Draw the contents of the widget. void QsciScintillaQt::paintEvent(QPaintEvent *e) { QSCI_SCI_NAMESPACE(Surface) *sw; const QRect &qr = e->rect(); rcPaint.left = qr.left(); rcPaint.top = qr.top(); rcPaint.right = qr.right() + 1; rcPaint.bottom = qr.bottom() + 1; QSCI_SCI_NAMESPACE(PRectangle) rcClient = GetClientRectangle(); paintingAllText = rcPaint.Contains(rcClient); sw = QSCI_SCI_NAMESPACE(Surface)::Allocate(SC_TECHNOLOGY_DEFAULT); if (!sw) return; QPainter painter(qsb->viewport()); paintState = painting; sw->Init(&painter); sw->SetUnicodeMode(CodePage() == SC_CP_UTF8); Paint(sw, rcPaint); delete sw; // If the painting area was insufficient to cover the new style or brace // highlight positions then repaint the whole thing. if (paintState == paintAbandoned) { // Do a full re-paint immediately. This may only be needed on OS X (to // avoid flicker). paintingAllText = true; sw = QSCI_SCI_NAMESPACE(Surface)::Allocate(SC_TECHNOLOGY_DEFAULT); if (!sw) return; QPainter painter(qsb->viewport()); paintState = painting; sw->Init(&painter); sw->SetUnicodeMode(CodePage() == SC_CP_UTF8); Paint(sw, rcPaint); delete sw; qsb->viewport()->update(); } paintState = notPainting; } // Re-implemented to drive the tickers. void QsciScintillaQt::timerEvent(QTimerEvent *e) { for (int i = 0; i <= static_cast(tickPlatform); ++i) if (timers[i] == e->timerId()) TickFor(static_cast(i)); } // Re-implemented to say we support fine tickers. bool QsciScintillaQt::FineTickerAvailable() { return true; } // Re-implemented to stop a ticker. void QsciScintillaQt::FineTickerCancel(TickReason reason) { int &ticker = timers[static_cast(reason)]; if (ticker != 0) { killTimer(ticker); ticker = 0; } } // Re-implemented to check if a particular ticker is running. bool QsciScintillaQt::FineTickerRunning(TickReason reason) { return (timers[static_cast(reason)] != 0); } // Re-implemented to start a ticker. void QsciScintillaQt::FineTickerStart(TickReason reason, int ms, int) { int &ticker = timers[static_cast(reason)]; if (ticker != 0) killTimer(ticker); ticker = startTimer(ms); } // Re-implemented to support idle processing. bool QsciScintillaQt::SetIdle(bool on) { if (on) { if (!idler.state) { QTimer *timer = reinterpret_cast(idler.idlerID); if (!timer) { idler.idlerID = timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(onIdle())); } timer->start(0); idler.state = true; } } else if (idler.state) { reinterpret_cast(idler.idlerID)->stop(); idler.state = false; } return true; } // Invoked to trigger any idle processing. void QsciScintillaQt::onIdle() { if (!Idle()) SetIdle(false); }