Bug 571740 - [GTK] StyledText: IllegalArgumentException on dead keys

The GTK's bug is in `gtk_im_context_simple_get_preedit_string()`, which
incorrectly returns `len`, which is expressed in bytes instead of
characters.

Note that on GNOME, SWT forces a different IM called "ibus" via
Bug 546349 and the bug is not seen there. The bug also won't trigger on
systems that are configured to use any other IM.

Change-Id: I4d24a62e9aa34e0ee6fe2d3ec1395f97aa89dfd6
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.swt/+/179531
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Soraphol (Paul) Damrongpiriyapong <sdamrong@redhat.com>
Reviewed-by: Alexander Kurtakov <akurtako@redhat.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/IME.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/IME.java
index cbd41b1..1a6faca 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/IME.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/IME.java
@@ -302,6 +302,19 @@
 		byte [] buffer = new byte [length];
 		C.memmove (buffer, preeditString [0], length);
 		chars = Converter.mbcsToWcs (buffer);
+
+		/*
+		 * Bug 571740: GTK has a bug in 'gtk-im-context-simple' IM context
+		 * which is default. It incorrectly reports 'cursorPos' in bytes
+		 * instead of characters. Somewhere around GTK 3.24.26 this IM
+		 * was reworked and it started sending 'gtk_preedit_changed' where
+		 * it previously did not, causing SWT to step on that old bug.
+		 * If caret's position is already at the end, this will result in
+		 * trying to set caret outside the text. The workaround is to limit
+		 * caret's position.
+		 */
+		caretOffset = Math.min(caretOffset, chars.length);
+
 		if (pangoAttrs [0] != 0) {
 			int count = 0;
 			long iterator = OS.pango_attr_list_get_iterator (pangoAttrs [0]);
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug571740_IAE_AccentChars.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug571740_IAE_AccentChars.java
new file mode 100644
index 0000000..e28081f
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug571740_IAE_AccentChars.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Syntevo and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Syntevo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.gtk.snippets;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.custom.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+public class Bug571740_IAE_AccentChars {
+	public static void main(String[] args) {
+		final Display display = new Display();
+
+		final Shell shell = new Shell(display);
+		shell.setLayout(new GridLayout(1, true));
+
+		Label hint = new Label(shell, 0);
+		hint.setText(
+			"1) Use GTK 3.24.26 or higher\n" +
+			"2) Do not use GNOME, because SWT forces 'ibus' IM there\n" +
+			"3) Do not have any IM method configured, so that GTK's default is used\n" +
+			"4) Install Spanish keyboard layout\n" +
+			"5) Switch to Spanish keyboard layout\n" +
+			"6) In StyledText below, type ' key according to US layout\n" +
+			"7) Before the patch, SWT will throw IAE\n" +
+			"8) After the patch or with XFCE, ´ character will (correctly) appear\n" +
+			"9) After the patch or with XFCE, if you now type A, ´ will (correctly) be replaced with á"
+		);
+
+		new StyledText(shell, SWT.BORDER);
+
+		shell.pack();
+		shell.open();
+
+		while (!shell.isDisposed()) {
+			if (!display.readAndDispatch()) {
+				display.sleep();
+			}
+		}
+
+		display.dispose();
+	}
+}