diff options
Diffstat (limited to 'qt/ScintillaEditBase/ScintillaQt.cpp')
-rw-r--r-- | qt/ScintillaEditBase/ScintillaQt.cpp | 772 |
1 files changed, 772 insertions, 0 deletions
diff --git a/qt/ScintillaEditBase/ScintillaQt.cpp b/qt/ScintillaEditBase/ScintillaQt.cpp new file mode 100644 index 000000000..e65390f4a --- /dev/null +++ b/qt/ScintillaEditBase/ScintillaQt.cpp @@ -0,0 +1,772 @@ +// +// Copyright (c) 1990-2011, Scientific Toolworks, Inc. +// +// The License.txt file describes the conditions under which this software may be distributed. +// +// Author: Jason Haslam +// +// Additions Copyright (c) 2011 Archaeopteryx Software, Inc. d/b/a Wingware +// ScintillaQt.cpp - Qt specific subclass of ScintillaBase + +#include "PlatQt.h" +#include "ScintillaQt.h" +#ifdef SCI_LEXER +#include "LexerModule.h" +#include "ExternalLexer.h" +#endif + +#include <QApplication> +#include <QDrag> +#include <QInputContext> +#include <QMimeData> +#include <QMenu> +#include <QScrollBar> +#include <QTimer> +#include <QTextCodec> + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + + +ScintillaQt::ScintillaQt(QAbstractScrollArea *parent) +: QObject(parent), scrollArea(parent), vMax(0), hMax(0), vPage(0), hPage(0), + haveMouseCapture(false), dragWasDropped(false) +{ + + wMain = scrollArea->viewport(); + + // On OS X drawing text into a pixmap moves it around 1 pixel to + // the right compared to drawing it directly onto a window. + // Buffered drawing turned off by default to avoid this. + WndProc(SCI_SETBUFFEREDDRAW, false, 0); + + Initialise(); +} + +ScintillaQt::~ScintillaQt() +{ + SetTicking(false); +} + +void ScintillaQt::tick() +{ + Tick(); +} + +void ScintillaQt::execCommand(QAction *action) +{ + int command = action->data().toInt(); + Command(command); +} + +#if defined(Q_OS_WIN) +static const QString sMSDEVColumnSelect("MSDEVColumnSelect"); +static const QString sWrappedMSDEVColumnSelect("application/x-qt-windows-mime;value=\"MSDEVColumnSelect\""); +#elif defined(Q_OS_MAC) +static const QString sScintillaRecPboardType("com.scintilla.utf16-plain-text.rectangular"); +static const QString sScintillaRecMimeType("text/x-scintilla.utf16-plain-text.rectangular"); +#else +// Linux +static const QString sMimeRectangularMarker("text/x-rectangular-marker"); +#endif + +#ifdef Q_OS_MAC + +class ScintillaRectangularMime : public QMacPasteboardMime { +public: + ScintillaRectangularMime() : QMacPasteboardMime(MIME_ALL) { + } + + QString convertorName() { + return QString("ScintillaRectangularMime"); + } + + bool canConvert(const QString &mime, QString flav) { + return mimeFor(flav) == mime; + } + + QString mimeFor(QString flav) { + if (flav == sScintillaRecPboardType) + return sScintillaRecMimeType; + return QString(); + } + + QString flavorFor(const QString &mime) { + if (mime == sScintillaRecMimeType) + return sScintillaRecPboardType; + return QString(); + } + + QVariant convertToMime(const QString & /* mime */, QList<QByteArray> data, QString /* flav */) { + QByteArray all; + foreach (QByteArray i, data) { + all += i; + } + return QVariant(all); + } + + QList<QByteArray> convertFromMime(const QString & /* mime */, QVariant data, QString /* flav */) { + QByteArray a = data.toByteArray(); + QList<QByteArray> l; + l.append(a); + return l; + } + +}; + +// The Mime object registers itself but only want one for all Scintilla instances. +// Should delete at exit to help memory leak detection but that would be extra work +// and, since the clipboard exists after last Scintilla instance, may be complex. +static ScintillaRectangularMime *singletonMime = 0; + +#endif + +void ScintillaQt::Initialise() +{ +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + rectangularSelectionModifier = SCMOD_ALT; +#else + rectangularSelectionModifier = SCMOD_CTRL; +#endif + +#ifdef Q_OS_MAC + if (!singletonMime) { + singletonMime = new ScintillaRectangularMime(); + + QStringList slTypes(sScintillaRecPboardType); + qRegisterDraggedTypes(slTypes); + } +#endif + + connect(QApplication::clipboard(), SIGNAL(selectionChanged()), + this, SLOT(SelectionChanged())); +} + +void ScintillaQt::Finalise() +{ + SetTicking(false); + ScintillaBase::Finalise(); +} + +void ScintillaQt::SelectionChanged() +{ + bool nowPrimary = QApplication::clipboard()->ownsSelection(); + if (nowPrimary != primarySelection) { + primarySelection = nowPrimary; + Redraw(); + } +} + +bool ScintillaQt::DragThreshold(Point ptStart, Point ptNow) +{ + int xMove = abs(ptStart.x - ptNow.x); + int yMove = abs(ptStart.y - ptNow.y); + return (xMove > QApplication::startDragDistance()) || + (yMove > QApplication::startDragDistance()); +} + +static QString StringFromSelectedText(const SelectionText &selectedText) +{ + if (selectedText.codePage == SC_CP_UTF8) { + return QString::fromUtf8(selectedText.s, selectedText.len-1); + } else { + QTextCodec *codec = QTextCodec::codecForName( + CharacterSetID(selectedText.characterSet)); + return codec->toUnicode(selectedText.s, selectedText.len-1); + } +} + +static void AddRectangularToMime(QMimeData *mimeData, QString su) +{ +#if defined(Q_OS_WIN) + // Add an empty marker + mimeData->setData(sMSDEVColumnSelect, QByteArray()); +#elif defined(Q_OS_MAC) + // OS X gets marker + data to work with other implementations. + // Don't understand how this works but it does - the + // clipboard format is supposed to be UTF-16, not UTF-8. + mimeData->setData(sScintillaRecMimeType, su.toUtf8()); +#else + Q_UNUSED(su); + // Linux + // Add an empty marker + mimeData->setData(sMimeRectangularMarker, QByteArray()); +#endif +} + +static bool IsRectangularInMime(const QMimeData *mimeData) +{ + QStringList formats = mimeData->formats(); + for (int i = 0; i < formats.size(); ++i) { +#if defined(Q_OS_WIN) + // Windows rectangular markers + // If rectangular copies made by this application, see base name. + if (formats[i] == sMSDEVColumnSelect) + return true; + // Otherwise see wrapped name. + if (formats[i] == sWrappedMSDEVColumnSelect) + return true; +#elif defined(Q_OS_MAC) + if (formats[i] == sScintillaRecMimeType) + return true; +#else + // Linux + if (formats[i] == sMimeRectangularMarker) + return true; +#endif + } + return false; +} + +bool ScintillaQt::ValidCodePage(int codePage) const +{ + return codePage == 0 + || codePage == SC_CP_UTF8 + || codePage == 932 + || codePage == 936 + || codePage == 949 + || codePage == 950 + || codePage == 1361; +} + + +void ScintillaQt::ScrollText(int linesToMove) +{ + int dy = vs.lineHeight * (linesToMove); + scrollArea->viewport()->scroll(0, dy); +} + +void ScintillaQt::SetVerticalScrollPos() +{ + scrollArea->verticalScrollBar()->setValue(topLine); + emit verticalScrolled(topLine); +} + +void ScintillaQt::SetHorizontalScrollPos() +{ + scrollArea->horizontalScrollBar()->setValue(xOffset); + emit horizontalScrolled(xOffset); +} + +bool ScintillaQt::ModifyScrollBars(int nMax, int nPage) +{ + bool modified = false; + + int vNewPage = nPage; + int vNewMax = nMax - vNewPage + 1; + if (vMax != vNewMax || vPage != vNewPage) { + vMax = vNewMax; + vPage = vNewPage; + modified = true; + + scrollArea->verticalScrollBar()->setMaximum(vMax); + scrollArea->verticalScrollBar()->setPageStep(vPage); + emit verticalRangeChanged(vMax, vPage); + } + + int hNewPage = GetTextRectangle().Width(); + int hNewMax = (scrollWidth > hNewPage) ? scrollWidth - hNewPage : 0; + int charWidth = vs.styles[STYLE_DEFAULT].aveCharWidth; + if (hMax != hNewMax || hPage != hNewPage || + scrollArea->horizontalScrollBar()->singleStep() != charWidth) { + hMax = hNewMax; + hPage = hNewPage; + modified = true; + + scrollArea->horizontalScrollBar()->setMaximum(hMax); + scrollArea->horizontalScrollBar()->setPageStep(hPage); + scrollArea->horizontalScrollBar()->setSingleStep(charWidth); + emit horizontalRangeChanged(hMax, hPage); + } + + return modified; +} + +void ScintillaQt::ReconfigureScrollBars() +{ + if (verticalScrollBarVisible) { + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } else { + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + + if (horizontalScrollBarVisible && (wrapState == eWrapNone)) { + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } else { + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } +} + +void ScintillaQt::CopyToModeClipboard(const SelectionText &selectedText, QClipboard::Mode clipboardMode_) +{ + QClipboard *clipboard = QApplication::clipboard(); + clipboard->clear(clipboardMode_); + QString su = StringFromSelectedText(selectedText); + QMimeData *mimeData = new QMimeData(); + mimeData->setText(su); + if (selectedText.rectangular) { + AddRectangularToMime(mimeData, su); + } + + // Allow client code to add additional data (e.g rich text). + emit aboutToCopy(mimeData); + + clipboard->setMimeData(mimeData, clipboardMode_); +} + +void ScintillaQt::Copy() +{ + if (!sel.Empty()) { + SelectionText st; + CopySelectionRange(&st); + CopyToClipboard(st); + } +} + +void ScintillaQt::CopyToClipboard(const SelectionText &selectedText) +{ + CopyToModeClipboard(selectedText, QClipboard::Clipboard); +} + +void ScintillaQt::PasteFromMode(QClipboard::Mode clipboardMode_) +{ + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(clipboardMode_); + bool isRectangular = IsRectangularInMime(mimeData); + QString text = clipboard->text(clipboardMode_); + QByteArray utext = BytesForDocument(text); + int len = utext.length(); + char *dest = Document::TransformLineEnds(&len, utext, len, pdoc->eolMode); + SelectionText selText; + selText.Set(dest, len, pdoc->dbcsCodePage, CharacterSetOfDocument(), isRectangular, false); + + UndoGroup ug(pdoc); + ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH); + SelectionPosition selStart = sel.IsRectangular() ? + sel.Rectangular().Start() : + sel.Range(sel.Main()).Start(); + if (selText.rectangular) { + PasteRectangular(selStart, selText.s, selText.len); + } else { + InsertPaste(selStart, selText.s, selText.len); + } + EnsureCaretVisible(); +} + +void ScintillaQt::Paste() +{ + PasteFromMode(QClipboard::Clipboard); +} + +void ScintillaQt::ClaimSelection() +{ + if (QApplication::clipboard()->supportsSelection()) { + // X Windows has a 'primary selection' as well as the clipboard. + // Whenever the user selects some text, we become the primary selection + if (!sel.Empty()) { + primarySelection = true; + SelectionText st; + CopySelectionRange(&st); + CopyToModeClipboard(st, QClipboard::Selection); + } else { + primarySelection = false; + } + } +} + +void ScintillaQt::NotifyChange() +{ + emit notifyChange(); + emit command( + Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE), + reinterpret_cast<sptr_t>(wMain.GetID())); +} + +void ScintillaQt::NotifyFocus(bool focus) +{ + emit command( + Platform::LongFromTwoShorts + (GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), + reinterpret_cast<sptr_t>(wMain.GetID())); +} + +void ScintillaQt::NotifyParent(SCNotification scn) +{ + scn.nmhdr.hwndFrom = wMain.GetID(); + scn.nmhdr.idFrom = GetCtrlID(); + emit notifyParent(scn); +} + +void ScintillaQt::SetTicking(bool on) +{ + QTimer *qTimer; + if (timer.ticking != on) { + timer.ticking = on; + if (timer.ticking) { + qTimer = new QTimer; + connect(qTimer, SIGNAL(timeout()), this, SLOT(tick())); + qTimer->start(timer.tickSize); + timer.tickerID = qTimer; + } else { + qTimer = static_cast<QTimer *>(timer.tickerID); + qTimer->stop(); + disconnect(qTimer, SIGNAL(timeout()), 0, 0); + delete qTimer; + timer.tickerID = 0; + } + } + timer.ticksToWait = caret.period; +} + +void ScintillaQt::onIdle() +{ + bool continueIdling = Idle(); + if (!continueIdling) { + SetIdle(false); + } +} + +bool ScintillaQt::SetIdle(bool on) +{ + QTimer *qIdle; + if (on) { + // Start idler, if it's not running. + if (!idler.state) { + idler.state = true; + qIdle = new QTimer; + connect(qIdle, SIGNAL(timeout()), this, SLOT(onIdle())); + qIdle->start(0); + idler.idlerID = qIdle; + } + } else { + // Stop idler, if it's running + if (idler.state) { + idler.state = false; + qIdle = static_cast<QTimer *>(idler.idlerID); + qIdle->stop(); + disconnect(qIdle, SIGNAL(timeout()), 0, 0); + delete qIdle; + idler.idlerID = 0; + } + } + return true; +} + +int ScintillaQt::CharacterSetOfDocument() const +{ + return vs.styles[STYLE_DEFAULT].characterSet; +} + +const char *ScintillaQt::CharacterSetIDOfDocument() const +{ + return CharacterSetID(CharacterSetOfDocument()); +} + +QString ScintillaQt::StringFromDocument(const char *s) const +{ + if (IsUnicodeMode()) { + return QString::fromUtf8(s); + } else { + QTextCodec *codec = QTextCodec::codecForName( + CharacterSetID(CharacterSetOfDocument())); + return codec->toUnicode(s); + } +} + +QByteArray ScintillaQt::BytesForDocument(const QString &text) const +{ + if (IsUnicodeMode()) { + return text.toUtf8(); + } else { + QTextCodec *codec = QTextCodec::codecForName( + CharacterSetID(CharacterSetOfDocument())); + return codec->fromUnicode(text); + } +} + + +class CaseFolderUTF8 : public CaseFolderTable { +public: + CaseFolderUTF8() { + StandardASCII(); + } + virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) { + if ((lenMixed == 1) && (sizeFolded > 0)) { + folded[0] = mapping[static_cast<unsigned char>(mixed[0])]; + return 1; + } else { + QString su = QString::fromUtf8(mixed, lenMixed); + QString suFolded = su.toCaseFolded(); + QByteArray bytesFolded = suFolded.toUtf8(); + if (bytesFolded.length() < static_cast<int>(sizeFolded)) { + memcpy(folded, bytesFolded, bytesFolded.length()); + return bytesFolded.length(); + } else { + folded[0] = '\0'; + return 0; + } + } + } +}; + +class CaseFolderDBCS : public CaseFolderTable { + QTextCodec *codec; +public: + CaseFolderDBCS(QTextCodec *codec_) : codec(codec_) { + StandardASCII(); + } + virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) { + if ((lenMixed == 1) && (sizeFolded > 0)) { + folded[0] = mapping[static_cast<unsigned char>(mixed[0])]; + return 1; + } else if (codec) { + QString su = codec->toUnicode(mixed, lenMixed); + QString suFolded = su.toCaseFolded(); + QByteArray bytesFolded = codec->fromUnicode(suFolded); + + if (bytesFolded.length() < static_cast<int>(sizeFolded)) { + memcpy(folded, bytesFolded, bytesFolded.length()); + return bytesFolded.length(); + } + } + // Something failed so return a single NUL byte + folded[0] = '\0'; + return 1; + } +}; + +CaseFolder *ScintillaQt::CaseFolderForEncoding() +{ + if (pdoc->dbcsCodePage == SC_CP_UTF8) { + return new CaseFolderUTF8(); + } else { + const char *charSetBuffer = CharacterSetIDOfDocument(); + if (charSetBuffer) { + if (pdoc->dbcsCodePage == 0) { + CaseFolderTable *pcf = new CaseFolderTable(); + pcf->StandardASCII(); + QTextCodec *codec = QTextCodec::codecForName(charSetBuffer); + // Only for single byte encodings + for (int i=0x80; i<0x100; i++) { + char sCharacter[2] = "A"; + sCharacter[0] = i; + QString su = codec->toUnicode(sCharacter, 1); + QString suFolded = su.toCaseFolded(); + QByteArray bytesFolded = codec->fromUnicode(suFolded); + if (bytesFolded.length() == 1) { + pcf->SetTranslation(sCharacter[0], bytesFolded[0]); + } + } + return pcf; + } else { + return new CaseFolderDBCS(QTextCodec::codecForName(charSetBuffer)); + } + } + return 0; + } +} + +std::string ScintillaQt::CaseMapString(const std::string &s, int caseMapping) +{ + if (s.size() == 0) + return std::string(); + + if (caseMapping == cmSame) + return s; + + QTextCodec *codec = 0; + QString text; + if (IsUnicodeMode()) { + text = QString::fromUtf8(s.c_str(), s.length()); + } else { + codec = QTextCodec::codecForName(CharacterSetIDOfDocument()); + text = codec->toUnicode(s.c_str(), s.length()); + } + + if (caseMapping == cmUpper) { + text = text.toUpper(); + } else { + text = text.toLower(); + } + + QByteArray bytes = BytesForDocument(text); + return std::string(bytes.data(), bytes.length()); +} + +void ScintillaQt::SetMouseCapture(bool on) +{ + // This is handled automatically by Qt + if (mouseDownCaptures) { + haveMouseCapture = on; + } +} + +bool ScintillaQt::HaveMouseCapture() +{ + return haveMouseCapture; +} + +void ScintillaQt::StartDrag() +{ + inDragDrop = ddDragging; + dropWentOutside = true; + if (drag.len) { + QMimeData *mimeData = new QMimeData; + QString sText = StringFromSelectedText(drag); + mimeData->setText(sText); + if (drag.rectangular) { + AddRectangularToMime(mimeData, sText); + } + // This QDrag is not freed as that causes a crash on Linux + QDrag *dragon = new QDrag(scrollArea); + dragon->setMimeData(mimeData); + + Qt::DropAction dropAction = dragon->exec(Qt::CopyAction|Qt::MoveAction); + if ((dropAction == Qt::MoveAction) && dropWentOutside) { + // Remove dragged out text + ClearSelection(); + } + } + inDragDrop = ddNone; + SetDragPosition(SelectionPosition(invalidPosition)); +} + +void ScintillaQt::CreateCallTipWindow(PRectangle rc) +{ + + if (!ct.wCallTip.Created()) { + QWidget *pCallTip = new QWidget(0, Qt::ToolTip); + ct.wCallTip = pCallTip; + pCallTip->move(rc.left, rc.top); + pCallTip->resize(rc.Width(), rc.Height()); + } +} + +void ScintillaQt::AddToPopUp(const char *label, + int cmd, + bool enabled) +{ + QMenu *menu = static_cast<QMenu *>(popup.GetID()); + QString text(label); + + if (text.isEmpty()) { + menu->addSeparator(); + } else { + QAction *action = menu->addAction(text); + action->setData(cmd); + action->setEnabled(enabled); + } + + // Make sure the menu's signal is connected only once. + menu->disconnect(); + connect(menu, SIGNAL(triggered(QAction *)), + this, SLOT(execCommand(QAction *))); +} + +sptr_t ScintillaQt::WndProc(unsigned int message, uptr_t wParam, sptr_t lParam) +{ + try { + switch (message) { + + case SCI_GRABFOCUS: + scrollArea->setFocus(Qt::OtherFocusReason); + break; + + case SCI_GETDIRECTFUNCTION: + return reinterpret_cast<sptr_t>(DirectFunction); + + case SCI_GETDIRECTPOINTER: + return reinterpret_cast<sptr_t>(this); + +#ifdef SCI_LEXER + case SCI_LOADLEXERLIBRARY: + LexerManager::GetInstance()->Load(reinterpret_cast<const char *>(lParam)); + break; +#endif + + default: + return ScintillaBase::WndProc(message, wParam, lParam); + } + } catch (std::bad_alloc &) { + errorStatus = SC_STATUS_BADALLOC; + } catch (...) { + errorStatus = SC_STATUS_FAILURE; + } + return 0l; +} + +sptr_t ScintillaQt::DefWndProc(unsigned int, uptr_t, sptr_t) +{ + return 0; +} + +sptr_t ScintillaQt::DirectFunction( + ScintillaQt *sciThis, unsigned int iMessage, uptr_t wParam, sptr_t lParam) +{ + return sciThis->WndProc(iMessage, wParam, lParam); +} + +// Additions to merge in Scientific Toolworks widget structure + +void ScintillaQt::PartialPaint(const PRectangle &rect) +{ + rcPaint = rect; + paintState = painting; + PRectangle rcClient = GetClientRectangle(); + paintingAllText = rcPaint.Contains(rcClient); + + AutoSurface surface(this); + Paint(surface, rcPaint); + surface->Release(); + + if (paintState == paintAbandoned) { + // FIXME: Failure to paint the requested rectangle in each + // paint event causes flicker on some platforms (Mac?) + // Paint rect immediately. + paintState = painting; + paintingAllText = true; + + AutoSurface surface(this); + Paint(surface, rcPaint); + surface->Release(); + + // Queue a full repaint. + scrollArea->viewport()->update(); + } + + paintState = notPainting; +} + +void ScintillaQt::DragEnter(const Point &point) +{ + SetDragPosition(SPositionFromLocation(point, + false, false, UserVirtualSpace())); +} + +void ScintillaQt::DragMove(const Point &point) +{ + SetDragPosition(SPositionFromLocation(point, + false, false, UserVirtualSpace())); +} + +void ScintillaQt::DragLeave() +{ + SetDragPosition(SelectionPosition(invalidPosition)); +} + +void ScintillaQt::Drop(const Point &point, const QMimeData *data, bool move) +{ + QString text = data->text(); + bool rectangular = IsRectangularInMime(data); + QByteArray bytes = BytesForDocument(text); + int len = bytes.length(); + char *dest = Document::TransformLineEnds(&len, bytes, len, pdoc->eolMode); + + SelectionPosition movePos = SPositionFromLocation(point, + false, false, UserVirtualSpace()); + + DropAt(movePos, dest, move, rectangular); + + delete []dest; +} |