diff options
author | Neil <nyamatongwe@gmail.com> | 2015-04-09 23:46:34 +1000 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2015-04-09 23:46:34 +1000 |
commit | 801a612e6cef4e45e7279c656169fe5bc92a7173 (patch) | |
tree | f5cea56b2d1b8ed6806e4231b3fbf9755fd4f4d2 /qt/ScintillaEditBase/ScintillaEditBase.cpp | |
parent | fa76ae534b15c7af787b79b146b0fd9ae95d36ce (diff) | |
download | scintilla-mirror-801a612e6cef4e45e7279c656169fe5bc92a7173.tar.gz |
Improvements to IME supporting multiple carets, handling Korean more
idiomatically and behaving more consistently with the other platforms.
From johnsonj.
Diffstat (limited to 'qt/ScintillaEditBase/ScintillaEditBase.cpp')
-rw-r--r-- | qt/ScintillaEditBase/ScintillaEditBase.cpp | 245 |
1 files changed, 174 insertions, 71 deletions
diff --git a/qt/ScintillaEditBase/ScintillaEditBase.cpp b/qt/ScintillaEditBase/ScintillaEditBase.cpp index 288d30dea..a99e06d81 100644 --- a/qt/ScintillaEditBase/ScintillaEditBase.cpp +++ b/qt/ScintillaEditBase/ScintillaEditBase.cpp @@ -23,6 +23,12 @@ #define INDIC_INPUTMETHOD 24 +#define MAXLENINPUTIME 200 +#define SC_INDICATOR_INPUT INDIC_IME +#define SC_INDICATOR_TARGET INDIC_IME+1 +#define SC_INDICATOR_CONVERTED INDIC_IME+2 +#define SC_INDICATOR_UNKNOWN INDIC_IME_MAX + #ifdef SCI_NAMESPACE using namespace Scintilla; #endif @@ -45,6 +51,11 @@ ScintillaEditBase::ScintillaEditBase(QWidget *parent) setAttribute(Qt::WA_KeyCompression); setAttribute(Qt::WA_InputMethodEnabled); + sqt->vs.indicators[SC_INDICATOR_UNKNOWN] = Indicator(INDIC_HIDDEN, ColourDesired(0, 0, 0xff)); + sqt->vs.indicators[SC_INDICATOR_INPUT] = Indicator(INDIC_DOTS, ColourDesired(0, 0, 0xff)); + sqt->vs.indicators[SC_INDICATOR_CONVERTED] = Indicator(INDIC_COMPOSITIONTHICK, ColourDesired(0, 0, 0xff)); + sqt->vs.indicators[SC_INDICATOR_TARGET] = Indicator(INDIC_STRAIGHTBOX, ColourDesired(0, 0, 0xff)); + connect(sqt, SIGNAL(notifyParent(SCNotification)), this, SLOT(notifyParent(SCNotification))); @@ -387,87 +398,179 @@ void ScintillaEditBase::dropEvent(QDropEvent *event) } } +bool ScintillaEditBase::IsHangul(const QChar qchar) +{ + int unicode = (int)qchar.unicode(); + // Korean character ranges used for preedit chars. + // http://www.programminginkorean.com/programming/hangul-in-unicode/ + const bool HangulJamo = (0x1100 <= unicode && unicode <= 0x11FF); + const bool HangulCompatibleJamo = (0x3130 <= unicode && unicode <= 0x318F); + const bool HangulJamoExtendedA = (0xA960 <= unicode && unicode <= 0xA97F); + const bool HangulJamoExtendedB = (0xD7B0 <= unicode && unicode <= 0xD7FF); + const bool HangulSyllable = (0xAC00 <= unicode && unicode <= 0xD7A3); + return HangulJamo || HangulCompatibleJamo || HangulSyllable || + HangulJamoExtendedA || HangulJamoExtendedB; +} + +void ScintillaEditBase::MoveImeCarets(int offset) +{ + // Move carets relatively by bytes + for (size_t r=0; r < sqt->sel.Count(); r++) { + int positionInsert = sqt->sel.Range(r).Start().Position(); + sqt->sel.Range(r).caret.SetPosition(positionInsert + offset); + sqt->sel.Range(r).anchor.SetPosition(positionInsert + offset); + } +} + +void ScintillaEditBase::DrawImeIndicator(int indicator, int len) +{ + // Emulate the visual style of IME characters with indicators. + // Draw an indicator on the character before caret by the character bytes of len + // so it should be called after AddCharUTF(). + // It does not affect caret positions. + if (indicator < 8 || indicator > INDIC_MAX) { + return; + } + sqt->pdoc->decorations.SetCurrentIndicator(indicator); + for (size_t r=0; r< sqt-> sel.Count(); r++) { + int positionInsert = sqt->sel.Range(r).Start().Position(); + sqt->pdoc->DecorationFillRange(positionInsert - len, 1, len); + } +} + 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, static_cast<unsigned int>(strlen(commitData))); + // Copy & paste by johnsonj with a lot of helps of Neil + // Great thanks for my forerunners, jiniya and BLUEnLIVE + + if (sqt->pdoc->TentativeActive()) { + sqt->pdoc->TentativeUndo(); + } else { + // No tentative undo means start of this composition so + // Fill in any virtual spaces. + sqt->FillVirtualSpace(); } - // 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, static_cast<unsigned int>(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()); + sqt->view.imeCaretBlockOverride = false; + + if (!event->commitString().isEmpty()) { + const QString commitStr = event->commitString(); + const unsigned int commitStrLen = commitStr.length(); + + for (unsigned int i = 0; i < commitStrLen;) { + const unsigned int ucWidth = commitStr.at(i).isHighSurrogate() ? 2 : 1; + const QString oneCharUTF16 = commitStr.mid(i, ucWidth); + const QByteArray oneChar = sqt->BytesForDocument(oneCharUTF16); + const int oneCharLen = oneChar.length(); + + sqt->AddCharUTF(oneChar.data(), oneCharLen); + i += ucWidth; + } + + } else if (!event->preeditString().isEmpty()) { + const QString preeditStr = event->preeditString(); + const unsigned int preeditStrLen = preeditStr.length(); + if ((preeditStrLen == 0) || (preeditStrLen > MAXLENINPUTIME)) { + sqt->ShowCaretAtCurrentPosition(); + return; + } + + sqt->pdoc->TentativeStart(); // TentativeActive() from now on. + + // Mark segments and get ime caret position. + unsigned int imeCaretPos = 0; + unsigned int imeIndicator[MAXLENINPUTIME] = {0}; +#ifdef Q_OS_LINUX + // ibus-qt has a bug to return only one underline style. + // Q_OS_LINUX blocks are temporary work around to cope with it. + unsigned int attrSegment = 0; +#endif + + foreach (QInputMethodEvent::Attribute attr, event->attributes()) { + if (attr.type == QInputMethodEvent::TextFormat) { + QTextFormat format = attr.value.value<QTextFormat>(); 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++; + unsigned int indicator = SC_INDICATOR_UNKNOWN; + switch (charFormat.underlineStyle()) { + case QTextCharFormat::NoUnderline: + indicator = SC_INDICATOR_TARGET; //target input + break; + case QTextCharFormat::SingleUnderline: + case QTextCharFormat::DashUnderline: + indicator = SC_INDICATOR_INPUT; //normal input + break; + case QTextCharFormat::DotLine: + case QTextCharFormat::DashDotLine: + case QTextCharFormat::WaveUnderline: + case QTextCharFormat::SpellCheckUnderline: + indicator = SC_INDICATOR_CONVERTED; + break; + + default: + indicator = SC_INDICATOR_UNKNOWN; } + +#ifdef Q_OS_LINUX + attrSegment++; + indicator = attr.start; +#endif + for (int i = attr.start; i < attr.start+attr.length; i++) { + imeIndicator[i] = indicator; + } + } else if (attr.type == QInputMethodEvent::Cursor) { + imeCaretPos = attr.start; } } +#ifdef Q_OS_LINUX + const bool targetInput = (attrSegment > 1) || ((imeCaretPos == 0) && (preeditStr != preeditString)); + preeditString = preeditStr; +#endif + // Display preedit characters one by one. + int imeCharPos[MAXLENINPUTIME] = {0}; + int numBytes = 0; + + const bool recording = sqt->recordingMacro; + sqt->recordingMacro = false; + for (unsigned int i = 0; i < preeditStrLen;) { + const unsigned int ucWidth = preeditStr.at(i).isHighSurrogate() ? 2 : 1; + const QString oneCharUTF16 = preeditStr.mid(i, ucWidth); + const QByteArray oneChar = sqt->BytesForDocument(oneCharUTF16); + const int oneCharLen = oneChar.length(); + + // Record character positions for moving ime caret. + numBytes += oneCharLen; + imeCharPos[i+1] = numBytes; + + sqt->AddCharUTF(oneChar.data(), oneCharLen); + +#ifdef Q_OS_LINUX + // Segment marked with imeCaretPos is for target input. + if ((imeIndicator[i] == imeCaretPos) && (targetInput)) { + DrawImeIndicator(SC_INDICATOR_TARGET, oneCharLen); + } else { + DrawImeIndicator(SC_INDICATOR_INPUT, oneCharLen); + } +#else + DrawImeIndicator(imeIndicator[i], oneCharLen); +#endif + i += ucWidth; + } + sqt->recordingMacro = recording; + + // Move IME carets. + if (IsHangul(preeditStr.at(0))) { + sqt->view.imeCaretBlockOverride = true; + MoveImeCarets(- imeCharPos[preeditStrLen]); + } else { + MoveImeCarets(- imeCharPos[preeditStrLen] + imeCharPos[imeCaretPos]); + } + + // Set candidate box position for Qt::ImMicroFocus. + preeditPos = sqt->CurrentPosition(); + updateMicroFocus(); } + sqt->ShowCaretAtCurrentPosition(); } QVariant ScintillaEditBase::inputMethodQuery(Qt::InputMethodQuery query) const |