Bug 494565: [GTK] Snippet243 doesn't work because Text#insert(String)
modifies topIndex

Methods that insert or select text should not modify the topIndex of the
viewer.

To avoid this issue we calculate the visible area, positions of the
topIndex, and the insertion/selection points. If the insertion/selection
points are outside the visible area, then scroll to them. Otherwise do
nothing, which preserves the topIndex.

Tested on GTK3.22, 3.20, 3.18, 3.16, 3.14, 3.10, and 2.24. No additional
AllNonBrowser JUnit test failures occur.

Change-Id: Ib020473a5a31d535fb0b1b84f8b13e087fa16c62
Signed-off-by: Eric Williams <ericwill@redhat.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
index be8c0b2..29c2e55 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
@@ -13861,6 +13861,26 @@
 }
 #endif
 
+#ifndef NO__1gtk_1text_1view_1get_1line_1yrange
+JNIEXPORT void JNICALL OS_NATIVE(_1gtk_1text_1view_1get_1line_1yrange)
+	(JNIEnv *env, jclass that, jintLong arg0, jbyteArray arg1, jintArray arg2, jintArray arg3)
+{
+	jbyte *lparg1=NULL;
+	jint *lparg2=NULL;
+	jint *lparg3=NULL;
+	OS_NATIVE_ENTER(env, that, _1gtk_1text_1view_1get_1line_1yrange_FUNC);
+	if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail;
+	if (arg2) if ((lparg2 = (*env)->GetIntArrayElements(env, arg2, NULL)) == NULL) goto fail;
+	if (arg3) if ((lparg3 = (*env)->GetIntArrayElements(env, arg3, NULL)) == NULL) goto fail;
+	gtk_text_view_get_line_yrange((GtkTextView *)arg0, (GtkTextIter *)lparg1, (gint *)lparg2, (gint *)lparg3);
+fail:
+	if (arg3 && lparg3) (*env)->ReleaseIntArrayElements(env, arg3, lparg3, 0);
+	if (arg2 && lparg2) (*env)->ReleaseIntArrayElements(env, arg2, lparg2, 0);
+	if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0);
+	OS_NATIVE_EXIT(env, that, _1gtk_1text_1view_1get_1line_1yrange_FUNC);
+}
+#endif
+
 #ifndef NO__1gtk_1text_1view_1get_1visible_1rect
 JNIEXPORT void JNICALL OS_NATIVE(_1gtk_1text_1view_1get_1visible_1rect)
 	(JNIEnv *env, jclass that, jintLong arg0, jobject arg1)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
index 9f5f7aa..1f4ae40 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
@@ -1109,6 +1109,7 @@
 	"_1gtk_1text_1view_1get_1iter_1at_1location",
 	"_1gtk_1text_1view_1get_1iter_1location",
 	"_1gtk_1text_1view_1get_1line_1at_1y",
+	"_1gtk_1text_1view_1get_1line_1yrange",
 	"_1gtk_1text_1view_1get_1visible_1rect",
 	"_1gtk_1text_1view_1get_1window",
 	"_1gtk_1text_1view_1new",
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
index 27e1284..29e60bd 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
@@ -1119,6 +1119,7 @@
 	_1gtk_1text_1view_1get_1iter_1at_1location_FUNC,
 	_1gtk_1text_1view_1get_1iter_1location_FUNC,
 	_1gtk_1text_1view_1get_1line_1at_1y_FUNC,
+	_1gtk_1text_1view_1get_1line_1yrange_FUNC,
 	_1gtk_1text_1view_1get_1visible_1rect_FUNC,
 	_1gtk_1text_1view_1get_1window_FUNC,
 	_1gtk_1text_1view_1new_FUNC,
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
index a09156b..bbcb5f2 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
@@ -11791,6 +11791,21 @@
 }
 /**
  * @param text_view cast=(GtkTextView *)
+ * @param target_iter cast=(GtkTextIter *)
+ * @param y cast=(gint *)
+ * @param height cast=(gint *)
+ */
+public static final native void _gtk_text_view_get_line_yrange(long /*int*/ text_view, byte[] target_iter, int[] y, int[] height);
+public static final void gtk_text_view_get_line_yrange(long /*int*/ text_view, byte[] target_iter, int[] y, int[] height) {
+	lock.lock();
+	try {
+		_gtk_text_view_get_line_yrange(text_view, target_iter, y, height);
+	} finally {
+		lock.unlock();
+	}
+}
+/**
+ * @param text_view cast=(GtkTextView *)
  * @param visible_rect cast=(GdkRectangle *),flags=no_in
  */
 public static final native void _gtk_text_view_get_visible_rect(long /*int*/ text_view, GdkRectangle visible_rect);
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Text.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Text.java
index 9173a53..facbbce 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Text.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Text.java
@@ -2027,12 +2027,72 @@
 		}
 		OS.gtk_text_buffer_insert (bufferHandle, start, buffer, buffer.length);
 		OS.gtk_text_buffer_place_cursor (bufferHandle, start);
-		long /*int*/ mark = OS.gtk_text_buffer_get_insert (bufferHandle);
-		OS.gtk_text_view_scroll_to_mark (handle, mark, 0, true, 0, 0);
+		scrollIfNotVisible(start, null, true);
 	}
 	applySegments ();
 }
 
+
+/**
+ * Methods that insert or select text should not modify the topIndex
+ * of the viewer.
+ *
+ * To avoid this issue we calculate the visible area, positions of the
+ * topIndex, and the insertion/selection points. If the insertion/selection points
+ * are outside the visible area, then scroll to them. Otherwise do nothing,
+ * which preserves the topIndex.
+ *
+ * @param iter the GtkTextIter representing the insertion/selection point
+ * @param scrollTo the GtkTextIter representing the point to be scrolled to (can be null)
+ * @param insert true if insertion is being performed, false if selection
+ *
+ */
+private void scrollIfNotVisible(byte [] iter, byte [] scrollTo, boolean insert) {
+	GdkRectangle rect = new GdkRectangle ();
+	int distanceTopIndex, numLinesVisible, lineHeight;
+	int[] insertionCoordinates = new int [1];
+	int[] topIndexCoordinates = new int [1];
+	byte [] indexIter =  new byte [ITER_SIZEOF];
+
+	// Calculate the visible area
+	OS.gtk_text_view_get_visible_rect (handle, rect);
+	lineHeight = getLineHeight ();
+	numLinesVisible = rect.height / lineHeight;
+
+	// Get the coordinates of the insertion/selection point
+	OS.gtk_text_view_get_line_yrange (handle, iter, insertionCoordinates, null);
+
+	// If we have a topIndex, calculate whether the insertion/selection point
+	// is in the visible area
+	if (indexMark != 0) {
+		OS.gtk_text_buffer_get_iter_at_mark (bufferHandle, indexIter, indexMark);
+		OS.gtk_text_view_get_line_yrange (handle, indexIter, topIndexCoordinates, null);
+		distanceTopIndex = (insertionCoordinates [0] - topIndexCoordinates [0]) / lineHeight;
+
+		// If it is not in the visible area, scroll to the insertion/selection point
+		if (distanceTopIndex >= numLinesVisible) {
+			if (insert) {
+				long /*int*/ mark = OS.gtk_text_buffer_get_insert (bufferHandle);
+				OS.gtk_text_view_scroll_to_mark (handle, mark, 0, true, 0, 0);
+			} else if (scrollTo != null) {
+				OS.gtk_text_view_scroll_to_iter (handle, scrollTo, 0, true, 0, 0);
+			}
+		}
+	} else {
+		// Set topIndex to 0 and perform visibility calculations based on that
+		topIndexCoordinates [0] = 0;
+		distanceTopIndex = (insertionCoordinates [0] - topIndexCoordinates [0]) / lineHeight;
+		if (distanceTopIndex >= numLinesVisible) {
+			if (scrollTo != null && !insert) {
+				OS.gtk_text_view_scroll_to_iter (handle, scrollTo, 0, true, 0, 0);
+			} else if (insert) {
+				long /*int*/ mark = OS.gtk_text_buffer_get_insert (bufferHandle);
+				OS.gtk_text_view_scroll_to_mark (handle, mark, 0, true, 0, 0);
+			}
+		}
+	}
+}
+
 @Override
 long /*int*/ paintWindow () {
 	if ((style & SWT.SINGLE) != 0) {
@@ -2461,8 +2521,7 @@
 		OS.g_free (ptr);
 		OS.gtk_text_buffer_get_iter_at_offset (bufferHandle, startIter, start);
 		OS.gtk_text_buffer_place_cursor (bufferHandle, startIter);
-		long /*int*/ mark = OS.gtk_text_buffer_get_insert (bufferHandle);
-		OS.gtk_text_view_scroll_to_mark (handle, mark, 0, true, 0, 0);
+		scrollIfNotVisible(startIter, startIter, false);
 	}
 }
 
@@ -2511,8 +2570,7 @@
 		OS.g_free (ptr);
 		OS.gtk_text_buffer_get_iter_at_offset (bufferHandle, startIter, start);
 		OS.gtk_text_buffer_get_iter_at_offset (bufferHandle, endIter, end);
-		// Bug 197785: scroll widget to start of selection using gtk_text_view_scroll_to_iter().
-		OS.gtk_text_view_scroll_to_iter (handle, startIter, 0, true, 0, 0);
+		scrollIfNotVisible(startIter, startIter, false);
 		OS.gtk_text_buffer_select_range(bufferHandle, startIter, endIter);
 	}
 }