diff options
author | Neil <nyamatongwe@gmail.com> | 2014-12-09 13:06:50 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2014-12-09 13:06:50 +1100 |
commit | aae8678f2c0dab6d9c725774d9a3c8e8ff988a7d (patch) | |
tree | 520f1a746c0d6dedfd5e9e8f4b5868621fb6aa1b | |
parent | cc80e372b35047e3c92acd1fd228de74dab3c7fb (diff) | |
download | scintilla-mirror-aae8678f2c0dab6d9c725774d9a3c8e8ff988a7d.tar.gz |
Using indicators for inline IME.
From johnsonj.
-rw-r--r-- | gtk/ScintillaGTK.cxx | 378 |
1 files changed, 251 insertions, 127 deletions
diff --git a/gtk/ScintillaGTK.cxx b/gtk/ScintillaGTK.cxx index d5d0622da..7ab638ada 100644 --- a/gtk/ScintillaGTK.cxx +++ b/gtk/ScintillaGTK.cxx @@ -89,6 +89,11 @@ #define IS_WIDGET_VISIBLE(w) (GTK_WIDGET_VISIBLE(w)) #endif +#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 + static GdkWindow *WindowFromWidget(GtkWidget *w) { #if GTK_CHECK_VERSION(3,0,0) return gtk_widget_get_window(w); @@ -291,12 +296,18 @@ private: gboolean ExposePreeditThis(GtkWidget *widget, GdkEventExpose *ose); static gboolean ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis); #endif + + bool KoreanIME(); void CommitThis(char *str); static void Commit(GtkIMContext *context, char *str, ScintillaGTK *sciThis); - void PreeditChangedThis(); + void PreeditChangedInlineThis(); + void PreeditChangedWindowedThis(); static void PreeditChanged(GtkIMContext *context, ScintillaGTK *sciThis); - - bool KoreanIME(); + void MoveImeCarets(int pos); + void DrawImeIndicator(int indicator, int len); + void GetImeUnderlines(PangoAttrList *attrs, bool *normalInput); + void GetImeBackgrounds(PangoAttrList *attrs, bool *targetInput); + void SetCandidateWindowPos(); static void StyleSetText(GtkWidget *widget, GtkStyle *previous, void*); static void RealizeText(GtkWidget *widget, void*); @@ -638,12 +649,20 @@ public: gchar *str; gint cursor_pos; PangoAttrList *attrs; + gboolean validUTF8; + glong uniStrLen; + gunichar *uniStr; + PangoScript pscript; PreEditString(GtkIMContext *im_context) { gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor_pos); + validUTF8 = g_utf8_validate(str, strlen(str), NULL); + uniStr = g_utf8_to_ucs4_fast(str, strlen(str), &uniStrLen); + pscript = pango_script_for_unichar(uniStr[0]); } ~PreEditString() { g_free(str); + g_free(uniStr); pango_attr_list_unref(attrs); } }; @@ -869,6 +888,10 @@ void ScintillaGTK::Initialise() { timers[tr].reason = tr; timers[tr].scintilla = this; } + vs.indicators[SC_INDICATOR_UNKNOWN] = Indicator(INDIC_HIDDEN, ColourDesired(0, 0, 0xff)); + vs.indicators[SC_INDICATOR_INPUT] = Indicator(INDIC_DOTS, ColourDesired(0, 0, 0xff)); + vs.indicators[SC_INDICATOR_CONVERTED] = Indicator(INDIC_COMPOSITIONTHICK, ColourDesired(0, 0, 0xff)); + vs.indicators[SC_INDICATOR_TARGET] = Indicator(INDIC_STRAIGHTBOX, ColourDesired(0, 0, 0xff)); } void ScintillaGTK::Finalise() { @@ -2323,64 +2346,128 @@ gboolean ScintillaGTK::ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, Sci #endif bool ScintillaGTK::KoreanIME() { - // Warn : for KoreanIME use only. - if (pdoc->TentativeActive()) { - return true; - } - - bool koreanIME = false; - PreEditString utfval(im_context); + PreEditString preeditStr(im_context); + PangoLanguage *pLang = pango_language_from_string("ko-KR"); // RFC-3066 + bool koreanIme = pango_language_includes_script(pLang, preeditStr.pscript); + return koreanIme; +} - // Only need to check if the first preedit char is Korean. - // The rest will be handled in TentativeActive() - // which can handle backspace and CJK commons and so forth. +void ScintillaGTK::MoveImeCarets(int pos) { + // Move carets relatively by bytes + for (size_t r=0; r<sel.Count(); r++) { + int positionInsert = sel.Range(r).Start().Position(); + sel.Range(r).caret.SetPosition(positionInsert + pos); + sel.Range(r).anchor.SetPosition(positionInsert + pos); + } +} - if (strlen(utfval.str) == 3) { // One hangul char has 3byte. - int unicode = UnicodeFromUTF8(reinterpret_cast<unsigned char *>(utfval.str)); - // Korean character ranges which are used for the first preedit chars. - // http://www.programminginkorean.com/programming/hangul-in-unicode/ - bool HangulJamo = (0x1100 <= unicode && unicode <= 0x11FF); - bool HangulCompatibleJamo = (0x3130 <= unicode && unicode <= 0x318F); - bool HangulJamoExtendedA = (0xA960 <= unicode && unicode <= 0xA97F); - bool HangulJamoExtendedB = (0xD7B0 <= unicode && unicode <= 0xD7FF); - bool HangulSyllable = (0xAC00 <= unicode && unicode <= 0xD7A3); - koreanIME = (HangulJamo | HangulCompatibleJamo | HangulSyllable - | HangulJamoExtendedA | HangulJamoExtendedB); +void ScintillaGTK::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; } - return koreanIME; + pdoc->decorations.SetCurrentIndicator(indicator); + for (size_t r=0; r<sel.Count(); r++) { + int positionInsert = sel.Range(r).Start().Position(); + pdoc->DecorationFillRange(positionInsert - len, 1, len); + } +} + +void ScintillaGTK::GetImeUnderlines(PangoAttrList *attrs, bool *normalInput) { + // Whether single underlines attribute is or not + // attr position is counted by the number of UTF-8 bytes + PangoAttrIterator *iterunderline = pango_attr_list_get_iterator(attrs); + if (iterunderline) { + do { + PangoAttribute *attrunderline = pango_attr_iterator_get(iterunderline, PANGO_ATTR_UNDERLINE); + if (attrunderline) { + glong start = attrunderline->start_index; + glong end = attrunderline->end_index; + PangoUnderline uline = (PangoUnderline)((PangoAttrInt *)attrunderline)->value; + for (glong i=start+1; i <= end; ++i) { + switch (uline) { + case PANGO_UNDERLINE_NONE: + normalInput[i] = false; + break; + case PANGO_UNDERLINE_SINGLE: // normal input + normalInput[i] = true; + break; + case PANGO_UNDERLINE_DOUBLE: + case PANGO_UNDERLINE_LOW: + case PANGO_UNDERLINE_ERROR: + break; + } + } + } + } while (pango_attr_iterator_next(iterunderline)); + pango_attr_iterator_destroy(iterunderline); + } +} + +void ScintillaGTK::GetImeBackgrounds(PangoAttrList *attrs, bool *targetInput) { + // Whether background color attribue is or not + // attr position is measured in UTF-8 bytes + PangoAttrIterator *itercolor = pango_attr_list_get_iterator(attrs); + if (itercolor) { + do { + PangoAttribute *backcolor = pango_attr_iterator_get(itercolor, PANGO_ATTR_BACKGROUND); + if (backcolor) { + glong start = backcolor->start_index; + glong end = backcolor->end_index; + for (glong i=start+1; i <= end; ++i) { + targetInput[i] = true; // target converted + } + } + } while (pango_attr_iterator_next(itercolor)); + pango_attr_iterator_destroy(itercolor); + } +} + +void ScintillaGTK::SetCandidateWindowPos() { + // Composition box accompanies candidate box. + Point pt = PointMainCaret(); + GdkRectangle imeBox = {0}; // No need to set width + imeBox.x = pt.x; // Only need positiion + imeBox.y = pt.y + vs.lineHeight; // underneath the first charater + gtk_im_context_set_cursor_location(im_context, &imeBox); } -void ScintillaGTK::CommitThis(char *utfVal) { +void ScintillaGTK::CommitThis(char *commitStr) { try { - //~ fprintf(stderr, "Commit '%s'\n", utfVal); + //~ fprintf(stderr, "Commit '%s'\n", commitStr); + view.imeCaretBlockOverride = false; + if (pdoc->TentativeActive()) { pdoc->TentativeUndo(); } - view.imeCaretBlockOverride = false; + const char *charSetSource = CharacterSetID(); - if (IsUnicodeMode()) { - AddCharUTF(utfVal, strlen(utfVal)); - } else { - const char *source = CharacterSetID(); - if (*source) { - Converter conv(source, "UTF-8", true); - if (conv) { - char localeVal[maxLenInputIME * 2]; - char *pin = utfVal; - size_t inLeft = strlen(utfVal); - char *pout = localeVal; - size_t outLeft = sizeof(localeVal); - size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft); - if (conversions != ((size_t)(-1))) { - *pout = '\0'; - AddCharUTF(localeVal, strlen(localeVal)); - } else { - fprintf(stderr, "Conversion failed '%s'\n", utfVal); - } - } + glong uniStrLen = 0; + gunichar *uniStr = g_utf8_to_ucs4_fast(commitStr, strlen(commitStr), &uniStrLen); + for (glong i = 0; i < uniStrLen; i++) { + + gunichar uniChar[1] = {0}; + uniChar[0] = uniStr[i]; + + glong oneCharLen = 0; + gchar *oneChar = g_ucs4_to_utf8(uniChar, 1, NULL, &oneCharLen, NULL); + + if (IsUnicodeMode()) { + // Do nothing ; + } else { + std::string oneCharSTD = ConvertText(oneChar, oneCharLen, charSetSource, "UTF-8", true); + oneCharLen = oneCharSTD.copy(oneChar,oneCharSTD.length(), 0); + oneChar[oneCharLen] = '\0'; } + + AddCharUTF(oneChar, oneCharLen); + g_free(oneChar); } + g_free(uniStr); ShowCaretAtCurrentPosition(); } catch (...) { errorStatus = SC_STATUS_FAILURE; @@ -2391,97 +2478,130 @@ void ScintillaGTK::Commit(GtkIMContext *, char *str, ScintillaGTK *sciThis) { sciThis->CommitThis(str); } -void ScintillaGTK::PreeditChangedThis() { +void ScintillaGTK::PreeditChangedInlineThis() { + // Copy & paste by johnsonj with a lot of helps of Neil + // Great thanks for my foreruners, jiniya and BLUEnLIVE try { - if (KoreanIME()) { - // Copy & paste by johnsonj. - // Great thanks to - // jiniya from http://www.jiniya.net/tt/494 for DBCS input with AddCharUTF(). - // BLUEnLIVE from http://zockr.tistory.com/1118 for UNDO and inOverstrike. - view.imeCaretBlockOverride = false; // If backspace. - - if (pdoc->TentativeActive()) { - pdoc->TentativeUndo(); - } else { - // No tentative undo means start of this composition so - // fill in any virtual spaces. - FillVirtualSpace(); - } + view.imeCaretBlockOverride = false; // If backspace. - PreEditString utfval(im_context); + if (pdoc->TentativeActive()) { + pdoc->TentativeUndo(); + } else { + // No tentative undo means start of this composition so + // fill in any virtual spaces. + FillVirtualSpace(); + } - if ((strlen(utfval.str) == 0) || strlen(utfval.str) > maxLenInputIME * 3) { - return; // Do not allow over 200 chars. - } + PreEditString preeditStr(im_context); + const char *charSetSource = CharacterSetID(); - char localeVal[maxLenInputIME * 2]; - char *hanval = (char *)""; + if (!preeditStr.validUTF8 || (charSetSource == NULL)) { + ShowCaretAtCurrentPosition(); + return; + } + + if (preeditStr.uniStrLen == 0 || preeditStr.uniStrLen > maxLenInputIME) { + //fprintf(stderr, "Do not allow over 200 chars: %i\n", preeditStr.uniStrLen); + ShowCaretAtCurrentPosition(); + return; + } + + pdoc->TentativeStart(); // TentativeActive() from now on + + // Get preedit string attribues + bool normalInput[maxLenInputIME*3+1] = {false}; + bool targetInput[maxLenInputIME*3+1] = {false}; + GetImeUnderlines(preeditStr.attrs, normalInput); + GetImeBackgrounds(preeditStr.attrs, targetInput); + + // Display preedit characters, one by one + glong imeCharPos[maxLenInputIME+1] = { 0 }; + glong attrPos = 0; + glong charWidth = 0; + + bool tmpRecordingMacro = recordingMacro; + recordingMacro = false; + for (glong i = 0; i < preeditStr.uniStrLen; i++) { + + gunichar uniChar[1] = {0}; + uniChar[0] = preeditStr.uniStr[i]; + + glong oneCharLen = 0; + gchar *oneChar = g_ucs4_to_utf8(uniChar, 1, NULL, &oneCharLen, NULL); + + // Record attribute positions in UTF-8 bytes + attrPos += oneCharLen; if (IsUnicodeMode()) { - hanval = utfval.str; + // Do nothing } else { - const char *source = CharacterSetID(); - if (*source) { - Converter conv(source, "UTF-8", true); - if (conv) { - char *pin = utfval.str; - size_t inLeft = strlen(utfval.str); - char *pout = localeVal; - size_t outLeft = sizeof(localeVal); - size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft); - if (conversions != ((size_t)(-1))) { - *pout = '\0'; - hanval = localeVal; - } else { - fprintf(stderr, "Conversion failed '%s'\n", utfval.str); - } - } - } + std::string oneCharSTD = ConvertText(oneChar, oneCharLen, charSetSource, "UTF-8", true); + oneCharLen = oneCharSTD.copy(oneChar,oneCharSTD.length(), 0); + oneChar[oneCharLen] = '\0'; } - if (!pdoc->TentativeActive()) { - pdoc->TentativeStart(); - } + // Record character positions in UTF-8 or DBCS bytes - bool tmpRecordingMacro = recordingMacro; - recordingMacro = false; - int hanlen = strlen(hanval); - AddCharUTF(hanval, hanlen); - recordingMacro = tmpRecordingMacro; + charWidth += oneCharLen; + imeCharPos[i+1] = charWidth; - // For block caret which means KoreanIME is in composition. - view.imeCaretBlockOverride = true; - for (size_t r = 0; r < sel.Count(); r++) { - int positionInsert = sel.Range(r).Start().Position(); - sel.Range(r).caret.SetPosition(positionInsert - hanlen); - sel.Range(r).anchor.SetPosition(positionInsert - hanlen); + // Display one character + AddCharUTF(oneChar, oneCharLen); + + // Draw an indicator on the character, + // Overlapping allowed + if (normalInput[attrPos]) { + DrawImeIndicator(SC_INDICATOR_INPUT, oneCharLen); } - } else { // Original code follows for other IMEs. - PreEditString pes(im_context); - if (strlen(pes.str) > 0) { - PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str); - pango_layout_set_attributes(layout, pes.attrs); - - gint w, h; - pango_layout_get_pixel_size(layout, &w, &h); - g_object_unref(layout); - - gint x, y; - gdk_window_get_origin(PWindow(wText), &x, &y); - - Point pt = PointMainCaret(); - if (pt.x < 0) - pt.x = 0; - if (pt.y < 0) - pt.y = 0; - - gtk_window_move(GTK_WINDOW(PWidget(wPreedit)), x + pt.x, y + pt.y); - gtk_window_resize(GTK_WINDOW(PWidget(wPreedit)), w, h); - gtk_widget_show(PWidget(wPreedit)); - gtk_widget_queue_draw_area(PWidget(wPreeditDraw), 0, 0, w, h); - } else { - gtk_widget_hide(PWidget(wPreedit)); + if (targetInput[attrPos]) { + DrawImeIndicator(SC_INDICATOR_TARGET, oneCharLen); } + g_free(oneChar); + } + recordingMacro = tmpRecordingMacro; + + // Move caret to ime cursor position. + if (KoreanIME()) { + view.imeCaretBlockOverride = true; + MoveImeCarets( - (imeCharPos[preeditStr.uniStrLen])); + + } else { + MoveImeCarets( - (imeCharPos[preeditStr.uniStrLen]) + imeCharPos[preeditStr.cursor_pos]); + } + + SetCandidateWindowPos(); + ShowCaretAtCurrentPosition(); + } catch (...) { + errorStatus = SC_STATUS_FAILURE; + } +} + +void ScintillaGTK::PreeditChangedWindowedThis() { + try { + PreEditString pes(im_context); + if (strlen(pes.str) > 0) { + PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str); + pango_layout_set_attributes(layout, pes.attrs); + + gint w, h; + pango_layout_get_pixel_size(layout, &w, &h); + g_object_unref(layout); + + gint x, y; + gdk_window_get_origin(PWindow(wText), &x, &y); + + Point pt = PointMainCaret(); + if (pt.x < 0) + pt.x = 0; + if (pt.y < 0) + pt.y = 0; + + gtk_window_move(GTK_WINDOW(PWidget(wPreedit)), x + pt.x, y + pt.y); + gtk_window_resize(GTK_WINDOW(PWidget(wPreedit)), w, h); + gtk_widget_show(PWidget(wPreedit)); + gtk_widget_queue_draw_area(PWidget(wPreeditDraw), 0, 0, w, h); + } else { + gtk_widget_hide(PWidget(wPreedit)); } } catch (...) { errorStatus = SC_STATUS_FAILURE; @@ -2489,7 +2609,11 @@ void ScintillaGTK::PreeditChangedThis() { } void ScintillaGTK::PreeditChanged(GtkIMContext *, ScintillaGTK *sciThis) { - sciThis->PreeditChangedThis(); + if (sciThis->imeInteraction == imeInline || sciThis->KoreanIME()) { + sciThis->PreeditChangedInlineThis(); + } else { + sciThis->PreeditChangedWindowedThis(); + } } void ScintillaGTK::StyleSetText(GtkWidget *widget, GtkStyle *, void*) { |