aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gtk/ScintillaGTK.cxx378
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*) {