diff options
Diffstat (limited to 'qt/ScintillaEditBase/ScintillaEditBase.cpp')
-rw-r--r-- | qt/ScintillaEditBase/ScintillaEditBase.cpp | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/qt/ScintillaEditBase/ScintillaEditBase.cpp b/qt/ScintillaEditBase/ScintillaEditBase.cpp new file mode 100644 index 000000000..b6cca8166 --- /dev/null +++ b/qt/ScintillaEditBase/ScintillaEditBase.cpp @@ -0,0 +1,635 @@ +// +// 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 +// ScintillaEditBase.cpp - Qt widget that wraps ScintillaQt and provides events and scrolling + +#include "ScintillaEditBase.h" +#include "ScintillaQt.h" +#include "PlatQt.h" + +#include <QApplication> +#include <QInputContext> +#include <QPainter> +#include <QScrollBar> +#include <QTextFormat> +#include <QVarLengthArray> + +#define INDIC_INPUTMETHOD 24 + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + +ScintillaEditBase::ScintillaEditBase(QWidget *parent) +: QAbstractScrollArea(parent), sqt(0), preeditPos(-1), wheelDelta(0) +{ + sqt = new ScintillaQt(this); + + time.start(); + + // Set Qt defaults. + setAcceptDrops(true); + setMouseTracking(true); + setAutoFillBackground(false); + setFrameStyle(QFrame::NoFrame); + setFocusPolicy(Qt::StrongFocus); + setAttribute(Qt::WA_StaticContents); + setAttribute(Qt::WA_OpaquePaintEvent); + setAttribute(Qt::WA_KeyCompression); + setAttribute(Qt::WA_InputMethodEnabled); + + connect(sqt, SIGNAL(notifyParent(SCNotification)), + this, SLOT(notifyParent(SCNotification))); + + // Connect scroll bars. + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(scrollVertical(int))); + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(scrollHorizontal(int))); + + // Connect pass-through signals. + connect(sqt, SIGNAL(horizontalRangeChanged(int,int)), + this, SIGNAL(horizontalRangeChanged(int,int))); + connect(sqt, SIGNAL(verticalRangeChanged(int,int)), + this, SIGNAL(verticalRangeChanged(int,int))); + connect(sqt, SIGNAL(horizontalScrolled(int)), + this, SIGNAL(horizontalScrolled(int))); + connect(sqt, SIGNAL(verticalScrolled(int)), + this, SIGNAL(verticalScrolled(int))); + + connect(sqt, SIGNAL(notifyChange()), + this, SIGNAL(notifyChange())); + + connect(sqt, SIGNAL(command(uptr_t, sptr_t)), + this, SLOT(event_command(uptr_t, sptr_t))); + + connect(sqt, SIGNAL(aboutToCopy(QMimeData *)), + this, SIGNAL(aboutToCopy(QMimeData *))); +} + +ScintillaEditBase::~ScintillaEditBase() {} + +sptr_t ScintillaEditBase::send( + unsigned int iMessage, + uptr_t wParam, + sptr_t lParam) const +{ + return sqt->WndProc(iMessage, wParam, lParam); +} + +sptr_t ScintillaEditBase::sends( + unsigned int iMessage, + uptr_t wParam, + const char *s) const +{ + return sqt->WndProc(iMessage, wParam, (sptr_t)s); +} + +void ScintillaEditBase::scrollHorizontal(int value) +{ + sqt->HorizontalScrollTo(value); +} + +void ScintillaEditBase::scrollVertical(int value) +{ + sqt->ScrollTo(value); +} + +bool ScintillaEditBase::event(QEvent *event) +{ + bool result = false; + + if (event->type() == QEvent::KeyPress) { + // Circumvent the tab focus convention. + keyPressEvent(static_cast<QKeyEvent *>(event)); + result = event->isAccepted(); + } else { + result = QAbstractScrollArea::event(event); + } + + return result; +} + +void ScintillaEditBase::paintEvent(QPaintEvent *event) +{ + sqt->PartialPaint(PRectFromQRect(event->rect())); +} + +void ScintillaEditBase::wheelEvent(QWheelEvent *event) +{ + if (event->orientation() == Qt::Horizontal) { + if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) + event->ignore(); + else + QAbstractScrollArea::wheelEvent(event); + } else { + if (event->modifiers() & Qt::ControlModifier) { + // Zoom! We play with the font sizes in the styles. + // Number of steps/line is ignored, we just care if sizing up or down + if (event->delta() > 0) { + sqt->KeyCommand(SCI_ZOOMIN); + } else { + sqt->KeyCommand(SCI_ZOOMOUT); + } + } else { + // Ignore wheel events when the scroll bars are disabled. + if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { + event->ignore(); + } else { + // Scroll + QAbstractScrollArea::wheelEvent(event); + } + } + } +} + +void ScintillaEditBase::focusInEvent(QFocusEvent *event) +{ + sqt->SetFocusState(true); + emit updateUi(); + + QAbstractScrollArea::focusInEvent(event); +} + +void ScintillaEditBase::focusOutEvent(QFocusEvent *event) +{ + sqt->SetFocusState(false); + + QAbstractScrollArea::focusOutEvent(event); +} + +void ScintillaEditBase::resizeEvent(QResizeEvent *) +{ + sqt->ChangeSize(); + emit resized(); +} + +void ScintillaEditBase::keyPressEvent(QKeyEvent *event) +{ + // All keystrokes containing the meta modifier are + // assumed to be shortcuts not handled by scintilla. + if (event->modifiers() & Qt::MetaModifier) { + QAbstractScrollArea::keyPressEvent(event); + emit keyPressed(event); + return; + } + + int key = 0; + switch (event->key()) { + case Qt::Key_Down: key = SCK_DOWN; break; + case Qt::Key_Up: key = SCK_UP; break; + case Qt::Key_Left: key = SCK_LEFT; break; + case Qt::Key_Right: key = SCK_RIGHT; break; + case Qt::Key_Home: key = SCK_HOME; break; + case Qt::Key_End: key = SCK_END; break; + case Qt::Key_PageUp: key = SCK_PRIOR; break; + case Qt::Key_PageDown: key = SCK_NEXT; break; + case Qt::Key_Delete: key = SCK_DELETE; break; + case Qt::Key_Insert: key = SCK_INSERT; break; + case Qt::Key_Escape: key = SCK_ESCAPE; break; + case Qt::Key_Backspace: key = SCK_BACK; break; + case Qt::Key_Plus: key = SCK_ADD; break; + case Qt::Key_Minus: key = SCK_SUBTRACT; break; + case Qt::Key_Backtab: // fall through + case Qt::Key_Tab: key = SCK_TAB; break; + case Qt::Key_Enter: // fall through + case Qt::Key_Return: key = SCK_RETURN; break; + case Qt::Key_Control: key = 0; break; + case Qt::Key_Alt: key = 0; break; + case Qt::Key_Shift: key = 0; break; + case Qt::Key_Meta: key = 0; break; + default: key = event->key(); break; + } + + bool shift = event->modifiers() & Qt::ShiftModifier; + bool ctrl = event->modifiers() & Qt::ControlModifier; + bool alt = event->modifiers() & Qt::AltModifier; + + bool consumed = false; + bool added = sqt->KeyDown(key, shift, ctrl, alt, &consumed) != 0; + if (!consumed) + consumed = added; + + if (!consumed) { + // Don't insert text if the control key was pressed unless + // it was pressed in conjunction with alt for AltGr emulation. + bool input = (!ctrl || alt); + + // Additionally, on non-mac platforms, don't insert text + // if the alt key was pressed unless control is also present. + // On mac alt can be used to insert special characters. +#ifndef Q_WS_MAC + input &= (!alt || ctrl); +#endif + + QString text = event->text(); + if (input && !text.isEmpty() && text[0].isPrint()) { + QByteArray utext = sqt->BytesForDocument(text); + sqt->AddCharUTF(utext.data(), utext.size()); + } else { + event->ignore(); + } + } + + emit keyPressed(event); +} + +static int modifierTranslated(int sciModifier) +{ + switch (sciModifier) { + case SCMOD_SHIFT: + return Qt::ShiftModifier; + case SCMOD_CTRL: + return Qt::ControlModifier; + case SCMOD_ALT: + return Qt::AltModifier; + case SCMOD_SUPER: + return Qt::MetaModifier; + default: + return 0; + } +} + +void ScintillaEditBase::mousePressEvent(QMouseEvent *event) +{ + Point pos = PointFromQPoint(event->pos()); + + emit buttonPressed(event); + + if (event->button() == Qt::MidButton && + QApplication::clipboard()->supportsSelection()) { + SelectionPosition selPos = sqt->SPositionFromLocation( + pos, false, false, sqt->UserVirtualSpace()); + sqt->sel.Clear(); + sqt->SetSelection(selPos, selPos); + sqt->PasteFromMode(QClipboard::Selection); + return; + } + + bool button = event->button() == Qt::LeftButton; + + if (button) { + bool shift = event->modifiers() & Qt::ShiftModifier; + bool ctrl = event->modifiers() & Qt::ControlModifier; +#ifdef Q_WS_X11 + // On X allow choice of rectangular modifier since most window + // managers grab alt + click for moving windows. + bool alt = event->modifiers() & modifierTranslated(sqt->rectangularSelectionModifier); +#else + bool alt = event->modifiers() & Qt::AltModifier; +#endif + + sqt->ButtonDown(pos, time.elapsed(), shift, ctrl, alt); + } +} + +void ScintillaEditBase::mouseReleaseEvent(QMouseEvent *event) +{ + Point point = PointFromQPoint(event->pos()); + bool ctrl = event->modifiers() & Qt::ControlModifier; + if (event->button() == Qt::LeftButton) + sqt->ButtonUp(point, time.elapsed(), ctrl); + + int pos = send(SCI_POSITIONFROMPOINT, point.x, point.y); + int line = send(SCI_LINEFROMPOSITION, pos); + int modifiers = event->modifiers(); + + emit textAreaClicked(line, modifiers); + emit buttonReleased(event); +} + +void ScintillaEditBase::mouseDoubleClickEvent(QMouseEvent *event) +{ + // Scintilla does its own double-click detection. + mousePressEvent(event); +} + +void ScintillaEditBase::mouseMoveEvent(QMouseEvent *event) +{ + Point pos = PointFromQPoint(event->pos()); + sqt->ButtonMove(pos); +} + +void ScintillaEditBase::contextMenuEvent(QContextMenuEvent *event) +{ + Point pos = PointFromQPoint(event->globalPos()); + Point pt = PointFromQPoint(event->pos()); + if (!sqt->PointInSelection(pt)) + sqt->SetEmptySelection(sqt->PositionFromLocation(pt)); + sqt->ContextMenu(pos); +} + +void ScintillaEditBase::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasText()) { + event->acceptProposedAction(); + + Point point = PointFromQPoint(event->pos()); + sqt->DragEnter(point); + } else { + event->ignore(); + } +} + +void ScintillaEditBase::dragLeaveEvent(QDragLeaveEvent * /* event */) +{ + sqt->DragLeave(); +} + +void ScintillaEditBase::dragMoveEvent(QDragMoveEvent *event) +{ + if (event->mimeData()->hasText()) { + event->acceptProposedAction(); + + Point point = PointFromQPoint(event->pos()); + sqt->DragMove(point); + } else { + event->ignore(); + } +} + +void ScintillaEditBase::dropEvent(QDropEvent *event) +{ + if (event->mimeData()->hasText()) { + event->acceptProposedAction(); + + Point point = PointFromQPoint(event->pos()); + bool move = (event->source() == this && + event->proposedAction() == Qt::MoveAction); + sqt->Drop(point, event->mimeData(), move); + } else { + event->ignore(); + } +} + +void ScintillaEditBase::inputMethodEvent(QInputMethodEvent *event) +{ + // Clear the current selection. + sqt->ClearSelection(); + if (preeditPos >= 0) + sqt->SetSelection(preeditPos, preeditPos); + + // Insert the commit string. + if (!event->commitString().isEmpty() || event->replacementLength()) { + // Select the text to be removed. + int commitPos = send(SCI_GETCURRENTPOS); + int start = commitPos + event->replacementStart(); + int end = start + event->replacementLength(); + sqt->SetSelection(start, end); + + // Replace the selection with the commit string. + QByteArray commitBytes = sqt->BytesForDocument(event->commitString()); + char *commitData = commitBytes.data(); + sqt->AddCharUTF(commitData, strlen(commitData)); + } + + // Select the previous preedit string. + int pos = send(SCI_GETCURRENTPOS); + int length = sqt->BytesForDocument(preeditString).length(); + sqt->SetSelection(pos, pos + length); + + // Replace the selection with the new preedit string. + QByteArray bytes = sqt->BytesForDocument(event->preeditString()); + char *data = bytes.data(); + bool recording = sqt->recordingMacro; + sqt->recordingMacro = false; + send(SCI_SETUNDOCOLLECTION, false); + sqt->AddCharUTF(data, strlen(data)); + send(SCI_SETUNDOCOLLECTION, true); + sqt->recordingMacro = recording; + sqt->SetSelection(pos, pos); + + // Store the state of the current preedit string. + preeditString = event->preeditString(); + preeditPos = !preeditString.isEmpty() ? send(SCI_GETCURRENTPOS) : -1; + + if (!preeditString.isEmpty()) { + // Apply attributes to the preedit string. + int indicNum = 0; + sqt->ShowCaretAtCurrentPosition(); + foreach (QInputMethodEvent::Attribute a, event->attributes()) { + QString prefix = preeditString.left(a.start); + QByteArray prefixBytes = sqt->BytesForDocument(prefix); + int prefixLength = prefixBytes.length(); + int caretPos = preeditPos + prefixLength; + + if (a.type == QInputMethodEvent::Cursor) { + sqt->SetSelection(caretPos, caretPos); + if (!a.length) + sqt->DropCaret(); + + } else if (a.type == QInputMethodEvent::TextFormat) { + Q_ASSERT(a.value.canConvert(QVariant::TextFormat)); + QTextFormat format = a.value.value<QTextFormat>(); + Q_ASSERT(format.isCharFormat()); + QTextCharFormat charFormat = format.toCharFormat(); + + QString sub = preeditString.mid(a.start, a.length); + QByteArray subBytes = sqt->BytesForDocument(sub); + int subLength = subBytes.length(); + + if (charFormat.underlineStyle() != QTextCharFormat::NoUnderline) { + // Set temporary indicator for underline style. + QColor uc = charFormat.underlineColor(); + int style = INDIC_PLAIN; + if (charFormat.underlineStyle() == QTextCharFormat::DashUnderline) + style = INDIC_DASH; + send(SCI_INDICSETSTYLE, INDIC_INPUTMETHOD + indicNum, style); + send(SCI_INDICSETFORE, INDIC_INPUTMETHOD + indicNum, uc.rgb()); + send(SCI_SETINDICATORCURRENT, INDIC_INPUTMETHOD + indicNum); + send(SCI_INDICATORFILLRANGE, caretPos, subLength); + indicNum++; + } + } + } + } +} + +QVariant ScintillaEditBase::inputMethodQuery(Qt::InputMethodQuery query) const +{ + int pos = send(SCI_GETCURRENTPOS); + int line = send(SCI_LINEFROMPOSITION, pos); + + switch (query) { + case Qt::ImMicroFocus: + { + int startPos = (preeditPos >= 0) ? preeditPos : pos; + Point pt = sqt->LocationFromPosition(startPos); + int width = send(SCI_GETCARETWIDTH); + int height = send(SCI_TEXTHEIGHT, line); + return QRect(pt.x, pt.y, width, height); + } + + case Qt::ImFont: + { + char fontName[64]; + int style = send(SCI_GETSTYLEAT, pos); + int len = send(SCI_STYLEGETFONT, style, (long)fontName); + int size = send(SCI_STYLEGETSIZE, style); + bool italic = send(SCI_STYLEGETITALIC, style); + int weight = send(SCI_STYLEGETBOLD, style) ? QFont::Bold : -1; + return QFont(QString::fromUtf8(fontName, len), size, weight, italic); + } + + case Qt::ImCursorPosition: + { + int paraStart = sqt->pdoc->ParaUp(pos); + return pos - paraStart; + } + + case Qt::ImSurroundingText: + { + int paraStart = sqt->pdoc->ParaUp(pos); + int paraEnd = sqt->pdoc->ParaDown(pos); + QVarLengthArray<char,1024> buffer(paraEnd - paraStart + 1); + + Sci_CharacterRange charRange; + charRange.cpMin = paraStart; + charRange.cpMax = paraEnd; + + Sci_TextRange textRange; + textRange.chrg = charRange; + textRange.lpstrText = buffer.data(); + + send(SCI_GETTEXTRANGE, 0, (long)&textRange); + + return sqt->StringFromDocument(buffer.constData()); + } + + case Qt::ImCurrentSelection: + { + QVarLengthArray<char,1024> buffer(send(SCI_GETSELTEXT)); + send(SCI_GETSELTEXT, 0, (long)buffer.data()); + + return sqt->StringFromDocument(buffer.constData()); + } + + default: + return QVariant(); + } +} + +void ScintillaEditBase::notifyParent(SCNotification scn) +{ + emit notify(&scn); + switch (scn.nmhdr.code) { + case SCN_STYLENEEDED: + emit styleNeeded(scn.position); + break; + + case SCN_CHARADDED: + emit charAdded(scn.ch); + break; + + case SCN_SAVEPOINTREACHED: + emit savePointChanged(false); + break; + + case SCN_SAVEPOINTLEFT: + emit savePointChanged(true); + break; + + case SCN_MODIFYATTEMPTRO: + emit modifyAttemptReadOnly(); + break; + + case SCN_KEY: + emit key(scn.ch); + break; + + case SCN_DOUBLECLICK: + emit doubleClick(scn.position, scn.line); + break; + + case SCN_UPDATEUI: + emit updateUi(); + break; + + case SCN_MODIFIED: + { + bool added = scn.modificationType & SC_MOD_INSERTTEXT; + bool deleted = scn.modificationType & SC_MOD_DELETETEXT; + + int length = send(SCI_GETTEXTLENGTH); + bool firstLineAdded = (added && length == 1) || + (deleted && length == 0); + + if (scn.linesAdded != 0) { + emit linesAdded(scn.linesAdded); + } else if (firstLineAdded) { + emit linesAdded(added ? 1 : -1); + } + + const QByteArray bytes = QByteArray::fromRawData(scn.text, scn.length); + emit modified(scn.modificationType, scn.position, scn.length, + scn.linesAdded, bytes, scn.line, + scn.foldLevelNow, scn.foldLevelPrev); + break; + } + + case SCN_MACRORECORD: + emit macroRecord(scn.message, scn.wParam, scn.lParam); + break; + + case SCN_MARGINCLICK: + emit marginClicked(scn.position, scn.modifiers, scn.margin); + break; + + case SCN_NEEDSHOWN: + emit needShown(scn.position, scn.length); + break; + + case SCN_PAINTED: + emit painted(); + break; + + case SCN_USERLISTSELECTION: + emit userListSelection(); + break; + + case SCN_URIDROPPED: + emit uriDropped(); + break; + + case SCN_DWELLSTART: + emit dwellStart(scn.x, scn.y); + break; + + case SCN_DWELLEND: + emit dwellEnd(scn.x, scn.y); + break; + + case SCN_ZOOM: + emit zoom(send(SCI_GETZOOM)); + break; + + case SCN_HOTSPOTCLICK: + emit hotSpotClick(scn.position, scn.modifiers); + break; + + case SCN_HOTSPOTDOUBLECLICK: + emit hotSpotDoubleClick(scn.position, scn.modifiers); + break; + + case SCN_CALLTIPCLICK: + emit callTipClick(); + break; + + case SCN_AUTOCSELECTION: + emit autoCompleteSelection(scn.lParam, QString::fromUtf8(scn.text)); + break; + + default: + return; + } +} + +void ScintillaEditBase::event_command(uptr_t wParam, sptr_t lParam) +{ + emit command(wParam, lParam); +} |