org.eclipse.swt.widgets.Text.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.widgets.Text.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.widgets;

import java.util.*;

import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.gtk.*;

/**
 * Instances of this class are selectable user interface
 * objects that allow the user to enter and modify text.
 * Text controls can be either single or multi-line.
 * When a text control is created with a border, the
 * operating system includes a platform specific inset
 * around the contents of the control.  When created
 * without a border, an effort is made to remove the
 * inset such that the preferred size of the control
 * is the same size as the contents.
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>CENTER, ICON_CANCEL, ICON_SEARCH, LEFT, MULTI, PASSWORD, SEARCH, SINGLE, RIGHT, READ_ONLY, WRAP</dd>
 * <dt><b>Events:</b></dt>
 * <dd>DefaultSelection, Modify, Verify, OrientationChange</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles MULTI and SINGLE may be specified,
 * and only one of the styles LEFT, CENTER, and RIGHT may be specified.
 * </p>
 * <p>
 * Note: The styles ICON_CANCEL and ICON_SEARCH are hints used in combination with SEARCH.
 * When the platform supports the hint, the text control shows these icons.  When an icon
 * is selected, a default selection event is sent with the detail field set to one of
 * ICON_CANCEL or ICON_SEARCH.  Normally, application code does not need to check the
 * detail.  In the case of ICON_CANCEL, the text is cleared before the default selection
 * event is sent causing the application to search for an empty string.
 * </p>
 * <p>
 * Note: Some text actions such as Undo are not natively supported on all platforms.
 * </p>
 * <p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @see <a href="http://www.eclipse.org/swt/snippets/#text">Text snippets</a>
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class Text extends Scrollable {
    long bufferHandle;
    long imContext;
    int tabs = 8, lastEventTime = 0;
    long gdkEventKey = 0;
    int fixStart = -1, fixEnd = -1;
    boolean doubleClick;
    String message = "";

    static final char LTR_MARK = '\u200e';
    static final char RTL_MARK = '\u200f';
    int[] segments;

    static final int ITER_SIZEOF = GTK.GtkTextIter_sizeof();
    static final int SPACE_FOR_CURSOR = 1;

    /**
    * The maximum number of characters that can be entered
    * into a text widget.
    * <p>
    * Note that this value is platform dependent, based upon
    * the native widget implementation.
    * </p>
    */
    public final static int LIMIT;
    /**
    * The delimiter used by multi-line text widgets.  When text
    * is queried and from the widget, it will be delimited using
    * this delimiter.
    */
    public final static String DELIMITER;
    /*
    * These values can be different on different platforms.
    * Therefore they are not initialized in the declaration
    * to stop the compiler from inlining.
    */
    static {
        LIMIT = 0x7FFFFFFF;
        DELIMITER = "\n";
    }
    /* Text uses non-standard CSS to set its background color, so we need
     * a global variable to keep track of its background color.
     */
    GdkRGBA background, foreground;
    long indexMark = 0;
    double cachedAdjustment, currentAdjustment;

    /**
     * Constructs a new instance of this class given its parent
     * and a style value describing its behavior and appearance.
     * <p>
     * The style value is either one of the style constants defined in
     * class <code>SWT</code> which is applicable to instances of this
     * class, or must be built by <em>bitwise OR</em>'ing together
     * (that is, using the <code>int</code> "|" operator) two or more
     * of those <code>SWT</code> style constants. The class description
     * lists the style constants that are applicable to the class.
     * Style bits are also inherited from superclasses.
     * </p>
     *
     * @param parent a composite control which will be the parent of the new instance (cannot be null)
     * @param style the style of control to construct
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
     *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
     * </ul>
     *
     * @see SWT#SINGLE
     * @see SWT#MULTI
     * @see SWT#READ_ONLY
     * @see SWT#WRAP
     * @see SWT#LEFT
     * @see SWT#RIGHT
     * @see SWT#CENTER
     * @see SWT#PASSWORD
     * @see SWT#SEARCH
     * @see SWT#ICON_SEARCH
     * @see SWT#ICON_CANCEL
     * @see Widget#checkSubclass
     * @see Widget#getStyle
     */
    public Text(Composite parent, int style) {
        super(parent, checkStyle(style));
        if ((style & SWT.SEARCH) != 0) {
            /*
             * Ensure that SWT.ICON_CANCEL and ICON_SEARCH are set.
             * NOTE: ICON_CANCEL has the same value as H_SCROLL and CON_SEARCH has the same value as V_SCROLL
             * so it is necessary to first clear these bits to avoid a scroll bar and then reset the
             * bit using the original style upplied by the programmer.
             *
             * NOTE2: Default GtkSearchEntry shows both "find" icon and "clear" icon.
             * "find" icon can be manually removed here while "clear" icon must be removed depending on text.
             * See gtk_changed.
             */
            this.style |= SWT.ICON_SEARCH | SWT.ICON_CANCEL;

            if ((style & SWT.ICON_SEARCH) == 0) {
                this.style &= ~SWT.ICON_SEARCH;
                GTK.gtk_entry_set_icon_from_icon_name(handle, GTK.GTK_ENTRY_ICON_PRIMARY, null);
            } else {
                // Default GtkSearchEntry always shows inactive "find" icon
                // make it active and sensitive to be consistent with other platforms
                GTK.gtk_entry_set_icon_activatable(handle, GTK.GTK_ENTRY_ICON_PRIMARY, true);
                GTK.gtk_entry_set_icon_sensitive(handle, GTK.GTK_ENTRY_ICON_PRIMARY, true);
            }

            if ((style & SWT.ICON_CANCEL) == 0) {
                this.style &= ~SWT.ICON_CANCEL;
            }
        }
    }

    static int checkStyle(int style) {
        if ((style & SWT.SEARCH) != 0) {
            style |= SWT.SINGLE | SWT.BORDER;
            style &= ~SWT.PASSWORD;
            /*
            * NOTE: ICON_CANCEL has the same value as H_SCROLL and
            * ICON_SEARCH has the same value as V_SCROLL so they are
            * cleared because SWT.SINGLE is set.
            */
        }
        if ((style & SWT.SINGLE) != 0 && (style & SWT.MULTI) != 0) {
            style &= ~SWT.MULTI;
        }
        style = checkBits(style, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0);
        if ((style & SWT.SINGLE) != 0)
            style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP);
        if ((style & SWT.WRAP) != 0) {
            style |= SWT.MULTI;
            style &= ~SWT.H_SCROLL;
        }
        if ((style & SWT.MULTI) != 0)
            style &= ~SWT.PASSWORD;
        if ((style & (SWT.SINGLE | SWT.MULTI)) != 0)
            return style;
        if ((style & (SWT.H_SCROLL | SWT.V_SCROLL)) != 0)
            return style | SWT.MULTI;
        return style | SWT.SINGLE;
    }

    @Override
    void createHandle(int index) {
        state |= HANDLE | MENU;
        if ((style & SWT.READ_ONLY) != 0) {
            if (applyThemeBackground() == 1) {
                state |= THEME_BACKGROUND;
            }
        }
        fixedHandle = OS.g_object_new(display.gtk_fixed_get_type(), 0);
        if (fixedHandle == 0)
            error(SWT.ERROR_NO_HANDLES);
        gtk_widget_set_has_surface_or_window(fixedHandle, true);
        if ((style & SWT.SINGLE) != 0) {
            if ((style & SWT.SEARCH) != 0) {
                handle = GTK.gtk_search_entry_new();
            } else {
                handle = GTK.gtk_entry_new();
            }
            if (handle == 0)
                error(SWT.ERROR_NO_HANDLES);
            GTK.gtk_container_add(fixedHandle, handle);
            GTK.gtk_editable_set_editable(handle, (style & SWT.READ_ONLY) == 0);
            /*
             * We need to handle borders differently in GTK3.20+. GtkEntry without frame will have a blank background color.
             * So let's set border via css and override the background in this case to be COLOR_LIST_BACKGROUND.
             */
            if (GTK.GTK_VERSION >= OS.VERSION(3, 20, 0)) {
                if ((style & SWT.BORDER) == 0) {
                    GTK.gtk_entry_set_has_frame(handle, false);
                    long context = GTK.gtk_widget_get_style_context(handle);
                    String background = display.gtk_rgba_to_css_string(display.COLOR_LIST_BACKGROUND_RGBA);
                    gtk_css_provider_load_from_css(context,
                            "entry {border: solid; background: " + background + ";}");
                    GTK.gtk_style_context_invalidate(context);
                }
            } else {
                GTK.gtk_entry_set_has_frame(handle, (style & SWT.BORDER) != 0);
            }
            GTK.gtk_entry_set_visibility(handle, (style & SWT.PASSWORD) == 0);
            float alignment = 0.0f;
            if ((style & SWT.CENTER) != 0)
                alignment = 0.5f;
            if ((style & SWT.RIGHT) != 0)
                alignment = 1.0f;
            if (alignment > 0.0f) {
                GTK.gtk_entry_set_alignment(handle, alignment);
            }
        } else {
            scrolledHandle = GTK.gtk_scrolled_window_new(0, 0);
            if (scrolledHandle == 0)
                error(SWT.ERROR_NO_HANDLES);
            handle = GTK.gtk_text_view_new();
            if (handle == 0)
                error(SWT.ERROR_NO_HANDLES);
            bufferHandle = GTK.gtk_text_view_get_buffer(handle);
            if (bufferHandle == 0)
                error(SWT.ERROR_NO_HANDLES);
            GTK.gtk_container_add(fixedHandle, scrolledHandle);
            GTK.gtk_container_add(scrolledHandle, handle);
            GTK.gtk_text_view_set_editable(handle, (style & SWT.READ_ONLY) == 0);
            if ((style & SWT.WRAP) != 0)
                GTK.gtk_text_view_set_wrap_mode(handle, GTK.GTK_WRAP_WORD_CHAR);
            int hsp = (style & SWT.H_SCROLL) != 0 ? GTK.GTK_POLICY_ALWAYS : GTK.GTK_POLICY_NEVER;
            int vsp = (style & SWT.V_SCROLL) != 0 ? GTK.GTK_POLICY_ALWAYS : GTK.GTK_POLICY_NEVER;
            GTK.gtk_scrolled_window_set_policy(scrolledHandle, hsp, vsp);
            if ((style & SWT.BORDER) != 0) {
                GTK.gtk_scrolled_window_set_shadow_type(scrolledHandle, GTK.GTK_SHADOW_ETCHED_IN);
            }
            int just = GTK.GTK_JUSTIFY_LEFT;
            if ((style & SWT.CENTER) != 0)
                just = GTK.GTK_JUSTIFY_CENTER;
            if ((style & SWT.RIGHT) != 0)
                just = GTK.GTK_JUSTIFY_RIGHT;
            GTK.gtk_text_view_set_justification(handle, just);
        }
        imContext = OS.imContextLast();
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_entry_set_width_chars(handle, 1);
        }
        // In GTK 3 font description is inherited from parent widget which is not how SWT has always worked,
        // reset to default font to get the usual behavior
        setFontDescription(defaultFont().handle);
    }

    @Override
    int applyThemeBackground() {
        return (backgroundAlpha == 0 || (style & (SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL)) == 0) ? 1 : 0;
    }

    @Override
    void createWidget(int index) {
        super.createWidget(index);
        doubleClick = true;
    }

    /**
     * Adds the listener to the collection of listeners who will
     * be notified when the receiver's text is modified, by sending
     * it one of the messages defined in the <code>ModifyListener</code>
     * interface.
     *
     * @param listener the listener which should be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see ModifyListener
     * @see #removeModifyListener
     */
    public void addModifyListener(ModifyListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Modify, typedListener);
    }

    /**
     * Adds a segment listener.
     * <p>
     * A <code>SegmentEvent</code> is sent whenever text content is being modified or
     * a segment listener is added or removed. You can
     * customize the appearance of text by indicating certain characters to be inserted
     * at certain text offsets. This may be used for bidi purposes, e.g. when
     * adjacent segments of right-to-left text should not be reordered relative to
     * each other.
     * E.g., multiple Java string literals in a right-to-left language
     * should generally remain in logical order to each other, that is, the
     * way they are stored.
     * </p>
     * <p>
     * <b>Warning</b>: This API is currently only implemented on Windows and GTK.
     * <code>SegmentEvent</code>s won't be sent on Cocoa.
     * </p>
     *
     * @param listener the listener which should be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SegmentEvent
     * @see SegmentListener
     * @see #removeSegmentListener
     *
     * @since 3.8
     */
    public void addSegmentListener(SegmentListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        addListener(SWT.Segments, new TypedListener(listener));
        clearSegments(true);
        applySegments();
    }

    /**
     * Adds the listener to the collection of listeners who will
     * be notified when the control is selected by the user, by sending
     * it one of the messages defined in the <code>SelectionListener</code>
     * interface.
     * <p>
     * <code>widgetSelected</code> is not called for texts.
     * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed in a single-line text,
     * or when ENTER is pressed in a search text. If the receiver has the <code>SWT.SEARCH | SWT.ICON_CANCEL</code> style
     * and the user cancels the search, the event object detail field contains the value <code>SWT.ICON_CANCEL</code>.
     * Likewise, if the receiver has the <code>SWT.ICON_SEARCH</code> style and the icon search is selected, the
     * event object detail field contains the value <code>SWT.ICON_SEARCH</code>.
     * </p>
     *
     * @param listener the listener which should be notified when the control is selected by the user
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #removeSelectionListener
     * @see SelectionEvent
     */
    public void addSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Selection, typedListener);
        addListener(SWT.DefaultSelection, typedListener);
    }

    /**
     * Adds the listener to the collection of listeners who will
     * be notified when the receiver's text is verified, by sending
     * it one of the messages defined in the <code>VerifyListener</code>
     * interface.
     *
     * @param listener the listener which should be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see VerifyListener
     * @see #removeVerifyListener
     */
    public void addVerifyListener(VerifyListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Verify, typedListener);
    }

    /**
     * Appends a string.
     * <p>
     * The new text is appended to the text at
     * the end of the widget.
     * </p>
     *
     * @param string the string to be appended
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void append(String string) {
        checkWidget();
        if (string == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        byte[] buffer = Converter.wcsToMbcs(string, false);
        clearSegments(true);
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_editable_insert_text(handle, buffer, buffer.length, new int[] { -1 });
            GTK.gtk_editable_set_position(handle, -1);
        } else {
            byte[] position = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_end_iter(bufferHandle, position);
            GTK.gtk_text_buffer_insert(bufferHandle, position, buffer, buffer.length);
            GTK.gtk_text_buffer_place_cursor(bufferHandle, position);
            long mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
            GTK.gtk_text_view_scroll_to_mark(handle, mark, 0, true, 0, 0);
        }
        applySegments();
    }

    void applySegments() {
        /*
         * It is possible (but unlikely), that application code could have
         * disposed the widget in the modify event. If this happens, return to
         * cancel the operation.
         */
        if (isDisposed() || (!hooks(SWT.Segments) && !filters(SWT.Segments)))
            return;
        Event event = new Event();
        String string = getText();
        event.text = string;
        event.segments = segments;
        sendEvent(SWT.Segments, event);
        segments = event.segments;
        if (segments == null)
            return;
        int nSegments = segments.length;
        if (nSegments == 0)
            return;

        for (int i = 1, length = string == null ? 0 : string.length(); i < nSegments; i++) {
            if (event.segments[i] < event.segments[i - 1] || event.segments[i] > length) {
                error(SWT.ERROR_INVALID_ARGUMENT);
            }
        }
        char[] segmentsChars = event.segmentsChars;
        char[] separator = { getOrientation() == SWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK };
        if ((style & SWT.SINGLE) != 0) {
            OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
            int limit = GTK.gtk_entry_get_max_length(handle);
            if (limit != 0)
                GTK.gtk_entry_set_max_length(handle, translateOffset(limit));
            int[] pos = new int[1];
            for (int i = 0; i < nSegments; i++) {
                pos[0] = segments[i] + i;
                if (segmentsChars != null && segmentsChars.length > i) {
                    separator[0] = segmentsChars[i];
                }
                byte[] buffer = Converter.wcsToMbcs(separator, false);
                long ptr = GTK.gtk_entry_get_text(handle);
                pos[0] = (int) OS.g_utf16_offset_to_utf8_offset(ptr, pos[0]);
                GTK.gtk_editable_insert_text(handle, buffer, buffer.length, pos);
            }
            OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
        } else {
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                    TEXT_BUFFER_INSERT_TEXT);
            byte[] pos = new byte[ITER_SIZEOF];
            byte[] startIter = new byte[ITER_SIZEOF];
            byte[] endIter = new byte[ITER_SIZEOF];
            for (int i = 0; i < nSegments; i++) {
                GTK.gtk_text_buffer_get_bounds(bufferHandle, startIter, endIter);
                long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, startIter, endIter, true);
                GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, pos,
                        (int) OS.g_utf16_offset_to_utf8_offset(ptr, segments[i] + i));
                OS.g_free(ptr);
                if (segmentsChars != null && segmentsChars.length > i) {
                    separator[0] = segmentsChars[i];
                }
                byte[] buffer = Converter.wcsToMbcs(separator, false);
                GTK.gtk_text_buffer_insert(bufferHandle, pos, buffer, buffer.length);
            }
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                    TEXT_BUFFER_INSERT_TEXT);
        }
    }

    void clearSegments(boolean applyText) {
        if (segments == null)
            return;
        int nSegments = segments.length;
        if (nSegments == 0)
            return;

        if ((style & SWT.SINGLE) != 0) {
            OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            if (applyText) {
                OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_TEXT);
                long ptr = GTK.gtk_entry_get_text(handle);
                int start, end;
                for (int i = 0; i < nSegments; i++) {
                    start = (int) OS.g_utf16_offset_to_utf8_offset(ptr, segments[i]);
                    end = (int) OS.g_utf16_offset_to_utf8_offset(ptr, segments[i] + 1);
                    GTK.gtk_editable_delete_text(handle, start, end);
                }
                OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_TEXT);
            }
            int limit = GTK.gtk_entry_get_max_length(handle);
            if (limit != 0)
                GTK.gtk_entry_set_max_length(handle, untranslateOffset(limit));
            OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
        } else if (applyText) {
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_RANGE);
            byte[] start = new byte[ITER_SIZEOF], end = new byte[ITER_SIZEOF];
            byte[] startIter = new byte[ITER_SIZEOF], endIter = new byte[ITER_SIZEOF];
            for (int i = 0; i < nSegments; i++) {
                GTK.gtk_text_buffer_get_bounds(bufferHandle, startIter, endIter);
                long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, startIter, endIter, true);
                GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, start,
                        (int) OS.g_utf16_offset_to_utf8_offset(ptr, segments[i]));
                GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, end,
                        (int) OS.g_utf16_offset_to_utf8_offset(ptr, segments[i] + 1));
                GTK.gtk_text_buffer_delete(bufferHandle, start, end);
                OS.g_free(ptr);
            }
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_RANGE);
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
        }
        segments = null;
    }

    /**
     * Clears the selection.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void clearSelection() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            int position = GTK.gtk_editable_get_position(handle);
            GTK.gtk_editable_select_region(handle, position, position);
        } else {
            byte[] position = new byte[ITER_SIZEOF];
            long insertMark = GTK.gtk_text_buffer_get_insert(bufferHandle);
            GTK.gtk_text_buffer_get_iter_at_mark(bufferHandle, position, insertMark);
            GTK.gtk_text_buffer_select_range(bufferHandle, position, position);
        }
    }

    @Override
    Point computeSizeInPixels(int wHint, int hHint, boolean changed) {
        checkWidget();
        if (wHint != SWT.DEFAULT && wHint < 0)
            wHint = 0;
        if (hHint != SWT.DEFAULT && hHint < 0)
            hHint = 0;
        int[] w = new int[1], h = new int[1];
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_widget_realize(handle);
            long layout = GTK.gtk_entry_get_layout(handle);
            OS.pango_layout_get_pixel_size(layout, w, h);
        } else {
            byte[] start = new byte[ITER_SIZEOF], end = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_bounds(bufferHandle, start, end);
            long text = GTK.gtk_text_buffer_get_text(bufferHandle, start, end, true);
            long layout = GTK.gtk_widget_create_pango_layout(handle, text);
            OS.g_free(text);
            OS.pango_layout_set_width(layout, wHint * OS.PANGO_SCALE);
            OS.pango_layout_get_pixel_size(layout, w, h);
            OS.g_object_unref(layout);
        }
        int width = w[0];
        int height = h[0];
        if ((style & SWT.SINGLE) != 0 && message.length() > 0) {
            byte[] buffer = Converter.wcsToMbcs(message, true);
            long layout = GTK.gtk_widget_create_pango_layout(handle, buffer);
            Arrays.fill(buffer, (byte) 0);
            OS.pango_layout_get_pixel_size(layout, w, h);
            OS.g_object_unref(layout);
            width = Math.max(width, w[0]);
        }
        if ((style & SWT.SEARCH) != 0) {
            // GtkSearchEntry have more padding than GtkEntry
            GtkBorder tmp = new GtkBorder();
            long context = GTK.gtk_widget_get_style_context(handle);
            int state_flag = GTK.GTK_VERSION < OS.VERSION(3, 18, 0) ? GTK.GTK_STATE_FLAG_NORMAL
                    : GTK.gtk_widget_get_state_flags(handle);
            gtk_style_context_get_padding(context, state_flag, tmp);
            width += tmp.left + tmp.right;
        }
        if (width == 0)
            width = DEFAULT_WIDTH;
        if (height == 0)
            height = DEFAULT_HEIGHT;
        width = wHint == SWT.DEFAULT ? width : wHint;
        height = hHint == SWT.DEFAULT ? height : hHint;
        Rectangle trim = computeTrimInPixels(0, 0, width, height);
        return new Point(trim.width, trim.height);
    }

    @Override
    Rectangle computeTrimInPixels(int x, int y, int width, int height) {
        checkWidget();
        Rectangle trim = super.computeTrimInPixels(x, y, width, height);
        int xborder = 0, yborder = 0;
        if ((style & SWT.SINGLE) != 0) {
            GtkBorder tmp = new GtkBorder();
            long context = GTK.gtk_widget_get_style_context(handle);
            int state_flag = GTK.GTK_VERSION < OS.VERSION(3, 18, 0) ? GTK.GTK_STATE_FLAG_NORMAL
                    : GTK.gtk_widget_get_state_flags(handle);
            gtk_style_context_get_padding(context, state_flag, tmp);
            trim.x -= tmp.left;
            trim.y -= tmp.top;
            trim.width += tmp.left + tmp.right;
            if (tmp.bottom == 0 && tmp.top == 0) {
                Point widthNative = computeNativeSize(handle, trim.width, SWT.DEFAULT, true);
                trim.height = widthNative.y;
            } else {
                trim.height += tmp.top + tmp.bottom;
            }
            if ((style & SWT.BORDER) != 0) {
                int state = GTK.GTK_VERSION < OS.VERSION(3, 18, 0) ? GTK.GTK_STATE_FLAG_NORMAL
                        : GTK.gtk_widget_get_state_flags(handle);
                gtk_style_context_get_border(context, state, tmp);
                trim.x -= tmp.left;
                trim.y -= tmp.top;
                trim.width += tmp.left + tmp.right;
                trim.height += tmp.top + tmp.bottom;
            }
            GdkRectangle icon_area = new GdkRectangle();
            GTK.gtk_entry_get_icon_area(handle, GTK.GTK_ENTRY_ICON_PRIMARY, icon_area);
            trim.x -= icon_area.width;
            trim.width += icon_area.width;
            GTK.gtk_entry_get_icon_area(handle, GTK.GTK_ENTRY_ICON_SECONDARY, icon_area);
            trim.width += icon_area.width;
        } else {
            int borderWidth = gtk_container_get_border_width_or_margin(handle);
            xborder += borderWidth;
            yborder += borderWidth;
        }
        /*
         * Focus line width is done via CSS in GTK4, and does not contribute
         * to the size of the widget.
         */
        if (!GTK.GTK4) {
            int[] property = new int[1];
            GTK.gtk_widget_style_get(handle, OS.interior_focus, property, 0);
            if (property[0] == 0) {
                GTK.gtk_widget_style_get(handle, OS.focus_line_width, property, 0);
                xborder += property[0];
                yborder += property[0];
            }
        }
        trim.x -= xborder;
        trim.y -= yborder;
        trim.width += 2 * xborder;
        trim.height += 2 * yborder;
        trim.width += SPACE_FOR_CURSOR;
        return new Rectangle(trim.x, trim.y, trim.width, trim.height);
    }

    /**
     * Copies the selected text.
     * <p>
     * The current selection is copied to the clipboard.
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void copy() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_editable_copy_clipboard(handle);
        } else {
            long clipboard = GTK.GTK4 ? GDK.gdk_display_get_clipboard(GDK.gdk_display_get_default())
                    : GTK.gtk_clipboard_get(GDK.GDK_NONE);
            ;
            clearSegments(true);
            GTK.gtk_text_buffer_copy_clipboard(bufferHandle, clipboard);
            applySegments();
        }
    }

    /**
     * Cuts the selected text.
     * <p>
     * The current selection is first copied to the
     * clipboard and then deleted from the widget.
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void cut() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_editable_cut_clipboard(handle);
        } else {
            long clipboard = GTK.GTK4 ? GDK.gdk_display_get_clipboard(GDK.gdk_display_get_default())
                    : GTK.gtk_clipboard_get(GDK.GDK_NONE);
            ;
            clearSegments(true);
            GTK.gtk_text_buffer_cut_clipboard(bufferHandle, clipboard, GTK.gtk_text_view_get_editable(handle));
            applySegments();
        }
    }

    char[] deprocessText(char[] text, int start, int end) {
        if (text == null)
            return new char[0];
        if (start < 0)
            start = 0;
        int length = text.length;
        if (end == -1)
            end = start + length;
        if (segments != null && end > segments[0]) {
            int nSegments = segments.length;
            if (nSegments > 0 && start <= segments[nSegments - 1]) {
                int nLeadSegments = 0;
                while (start - nLeadSegments > segments[nLeadSegments])
                    nLeadSegments++;
                int segmentCount = nLeadSegments;
                for (int i = start; i < end; i++) {
                    if (segmentCount < nSegments && i - segmentCount == segments[segmentCount]) {
                        ++segmentCount;
                    } else {
                        text[i - segmentCount + nLeadSegments - start] = text[i - start];
                    }
                }
                length = end - start - segmentCount + nLeadSegments;
            }
        }
        if (start != 0 || end != start + length) {
            char[] newText = new char[length];
            System.arraycopy(text, 0, newText, 0, length);
            return newText;
        }
        return text;
    }

    @Override
    GdkRGBA defaultBackground() {
        return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).handle;
    }

    @Override
    void deregister() {
        super.deregister();
        if (bufferHandle != 0)
            display.removeWidget(bufferHandle);
        long imContext = imContext();
        if (imContext != 0)
            display.removeWidget(imContext);
    }

    @Override
    boolean dragDetect(int x, int y, boolean filter, boolean dragOnTimeout, boolean[] consume) {
        /**
         * Drag detection on GTKText will not be done in SWT, but fully handled by GTK side.
         * Let GTK handle the DnD logic as it is inherent to that widget as of GTK3.14 and cannot
         * be removed without overriding it. It is better to take the signal from GTK and send it
         * back to SWT instead.
         */
        boolean isDraggable = (GTK.GTK_VERSION < OS.VERSION(3, 14, 0)) ? insideBlockSelection(x, y) : false;
        if (filter) {
            if (isDraggable && super.dragDetect(x, y, filter, dragOnTimeout, consume)) {
                if (consume != null)
                    consume[0] = true;
                return true;
            }
        }
        return false;
    }

    @Override
    long eventWindow() {
        return paintWindow();
    }

    @Override
    boolean filterKey(int keyval, long event) {
        int time = GDK.gdk_event_get_time(event);
        if (time != lastEventTime) {
            lastEventTime = time;
            long imContext = imContext();
            if (imContext != 0) {
                return GTK.gtk_im_context_filter_keypress(imContext, event);
            }
        }
        gdkEventKey = event;
        return false;
    }

    void fixIM() {
        /*
        *  The IM filter has to be called one time for each key press event.
        *  When the IM is open the key events are duplicated. The first event
        *  is filtered by SWT and the second event is filtered by GTK.  In some
        *  cases the GTK handler does not run (the widget is destroyed, the
        *  application code consumes the event, etc), for these cases the IM
        *  filter has to be called by SWT.
        */
        if (gdkEventKey != 0 && gdkEventKey != -1) {
            long imContext = imContext();
            if (imContext != 0) {
                GTK.gtk_im_context_filter_keypress(imContext, gdkEventKey);
                gdkEventKey = -1;
                return;
            }
        }
        gdkEventKey = 0;
    }

    @Override
    int getBorderWidthInPixels() {
        checkWidget();
        if ((style & SWT.MULTI) != 0)
            return super.getBorderWidthInPixels();
        if ((this.style & SWT.BORDER) != 0) {
            return getThickness(handle).x;
        }
        return 0;
    }

    /**
     * Returns the line number of the caret.
     * <p>
     * The line number of the caret is returned.
     * </p>
     *
     * @return the line number
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getCaretLineNumber() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0)
            return 0;
        byte[] position = new byte[ITER_SIZEOF];
        long mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
        GTK.gtk_text_buffer_get_iter_at_mark(bufferHandle, position, mark);
        return GTK.gtk_text_iter_get_line(position);
    }

    /**
     * Returns a point describing the location of the caret relative
     * to the receiver.
     *
     * @return a point, the location of the caret
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public Point getCaretLocation() {
        checkWidget();
        return DPIUtil.autoScaleDown(getCaretLocationInPixels());
    }

    Point getCaretLocationInPixels() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            int index = GTK.gtk_editable_get_position(handle);
            index = GTK.gtk_entry_text_index_to_layout_index(handle, index);
            int[] offset_x = new int[1], offset_y = new int[1];
            GTK.gtk_entry_get_layout_offsets(handle, offset_x, offset_y);
            long layout = GTK.gtk_entry_get_layout(handle);
            PangoRectangle pos = new PangoRectangle();
            OS.pango_layout_index_to_pos(layout, index, pos);
            int x = offset_x[0] + OS.PANGO_PIXELS(pos.x) - getBorderWidthInPixels();
            int y = offset_y[0] + OS.PANGO_PIXELS(pos.y);
            return new Point(x, y);
        }
        byte[] position = new byte[ITER_SIZEOF];
        long mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
        GTK.gtk_text_buffer_get_iter_at_mark(bufferHandle, position, mark);
        GdkRectangle rect = new GdkRectangle();
        GTK.gtk_text_view_get_iter_location(handle, position, rect);
        int[] x = new int[1];
        int[] y = new int[1];
        GTK.gtk_text_view_buffer_to_window_coords(handle, GTK.GTK_TEXT_WINDOW_TEXT, rect.x, rect.y, x, y);
        return new Point(x[0], y[0]);
    }

    /**
     * Returns the character position of the caret.
     * <p>
     * Indexing is zero based.
     * </p>
     *
     * @return the position of the caret
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getCaretPosition() {
        checkWidget();
        int result;
        if ((style & SWT.SINGLE) != 0) {
            long ptr = GTK.gtk_entry_get_text(handle);
            result = (int) OS.g_utf8_offset_to_utf16_offset(ptr, GTK.gtk_editable_get_position(handle));
        } else {
            byte[] position = new byte[ITER_SIZEOF];
            long mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
            GTK.gtk_text_buffer_get_iter_at_mark(bufferHandle, position, mark);
            byte[] zero = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, zero, 0);
            long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, zero, position, true);
            result = (int) OS.g_utf8_offset_to_utf16_offset(ptr, GTK.gtk_text_iter_get_offset(position));
            OS.g_free(ptr);
        }
        return untranslateOffset(result);
    }

    /**
     * Returns the number of characters.
     *
     * @return number of characters in the widget
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getCharCount() {
        checkWidget();
        int result;
        if ((style & SWT.SINGLE) != 0) {
            long ptr = GTK.gtk_entry_get_text(handle);
            result = (int) OS.g_utf16_strlen(ptr, -1);
        } else {
            byte[] startIter = new byte[ITER_SIZEOF];
            byte[] endIter = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_bounds(bufferHandle, startIter, endIter);
            long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, startIter, endIter, true);
            result = (int) OS.g_utf16_strlen(ptr, -1);
            OS.g_free(ptr);
        }
        return untranslateOffset(result);
    }

    /**
     * Returns the double click enabled flag.
     * <p>
     * The double click flag enables or disables the
     * default action of the text widget when the user
     * double clicks.
     * </p>
     *
     * @return whether or not double click is enabled
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public boolean getDoubleClickEnabled() {
        checkWidget();
        return doubleClick;
    }

    /**
     * Returns the echo character.
     * <p>
     * The echo character is the character that is
     * displayed when the user enters text or the
     * text is changed by the programmer.
     * </p>
     *
     * @return the echo character
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setEchoChar
     */
    public char getEchoChar() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            if (!GTK.gtk_entry_get_visibility(handle)) {
                return GTK.gtk_entry_get_invisible_char(handle);
            }
        }
        return '\0';
    }

    /**
     * Returns the editable state.
     *
     * @return whether or not the receiver is editable
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public boolean getEditable() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            return GTK.gtk_editable_get_editable(handle);
        }
        return GTK.gtk_text_view_get_editable(handle);
    }

    /**
     * Returns the number of lines.
     *
     * @return the number of lines in the widget
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getLineCount() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0)
            return 1;
        return GTK.gtk_text_buffer_get_line_count(bufferHandle);
    }

    /**
     * Returns the line delimiter.
     *
     * @return a string that is the line delimiter
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #DELIMITER
     */
    public String getLineDelimiter() {
        checkWidget();
        return "\n";
    }

    /**
     * Returns the height of a line.
     *
     * @return the height of a row of text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getLineHeight() {
        checkWidget();
        return fontHeight(getFontDescription(), handle);
    }

    /**
     * Returns the widget message.  The message text is displayed
     * as a hint for the user, indicating the purpose of the field.
     * <p>
     * Typically this is used in conjunction with <code>SWT.SEARCH</code>.
     * </p>
     *
     * @return the widget message
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.3
     */
    public String getMessage() {
        checkWidget();
        return message;
    }

    /**
     * Returns the orientation of the receiver, which will be one of the
     * constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
     *
     * @return the orientation style
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 2.1.2
     */
    @Override
    public int getOrientation() {
        return super.getOrientation();
    }

    /*public*/ int getPosition(Point point) {
        checkWidget();
        if (point == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        int position = -1;
        if ((style & SWT.SINGLE) != 0) {
            int[] index = new int[1];
            int[] trailing = new int[1];
            long layout = GTK.gtk_entry_get_layout(handle);
            OS.pango_layout_xy_to_index(layout, point.x * OS.PANGO_SCALE, point.y * OS.PANGO_SCALE, index,
                    trailing);
            long ptr = OS.pango_layout_get_text(layout);
            position = (int) OS.g_utf16_pointer_to_offset(ptr, ptr + index[0]) + trailing[0];
        } else {
            byte[] p = new byte[ITER_SIZEOF];
            GTK.gtk_text_view_get_iter_at_location(handle, p, point.x, point.y);
            byte[] zero = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, zero, 0);
            long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, zero, p, true);
            position = (int) OS.g_utf8_offset_to_utf16_offset(ptr, GTK.gtk_text_iter_get_offset(p));
            OS.g_free(ptr);
        }
        return untranslateOffset(position);
    }

    /**
     * Returns a <code>Point</code> whose x coordinate is the
     * character position representing the start of the selected
     * text, and whose y coordinate is the character position
     * representing the end of the selection. An "empty" selection
     * is indicated by the x and y coordinates having the same value.
     * <p>
     * Indexing is zero based.  The range of a selection is from
     * 0..N where N is the number of characters in the widget.
     * </p>
     *
     * @return a point representing the selection start and end
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public Point getSelection() {
        checkWidget();
        Point selection;
        if ((style & SWT.SINGLE) != 0) {
            int[] start = new int[1];
            int[] end = new int[1];
            GTK.gtk_editable_get_selection_bounds(handle, start, end);
            long ptr = GTK.gtk_entry_get_text(handle);
            start[0] = (int) OS.g_utf8_offset_to_utf16_offset(ptr, start[0]);
            end[0] = (int) OS.g_utf8_offset_to_utf16_offset(ptr, end[0]);
            selection = new Point(start[0], end[0]);
        } else {
            byte[] startIter = new byte[ITER_SIZEOF];
            byte[] endIter = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_selection_bounds(bufferHandle, startIter, endIter);
            byte[] zero = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, zero, 0);
            long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, zero, endIter, true);
            int start = (int) OS.g_utf8_offset_to_utf16_offset(ptr, GTK.gtk_text_iter_get_offset(startIter));
            int end = (int) OS.g_utf8_offset_to_utf16_offset(ptr, GTK.gtk_text_iter_get_offset(endIter));
            OS.g_free(ptr);
            selection = new Point(start, end);
        }
        selection.x = untranslateOffset(selection.x);
        selection.y = untranslateOffset(selection.y);
        return selection;
    }

    /**
     * Returns the number of selected characters.
     *
     * @return the number of selected characters.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getSelectionCount() {
        checkWidget();
        Point selection = getSelection();
        return Math.abs(selection.y - selection.x);
    }

    /**
     * Gets the selected text, or an empty string if there is no current selection.
     *
     * @return the selected text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public String getSelectionText() {
        checkWidget();
        Point selection = getSelection();
        return getText().substring(selection.x, selection.y);
    }

    /**
     * Returns the number of tabs.
     * <p>
     * Tab stop spacing is specified in terms of the
     * space (' ') character.  The width of a single
     * tab stop is the pixel width of the spaces.
     * </p>
     *
     * @return the number of tab characters
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getTabs() {
        checkWidget();
        return tabs;
    }

    int getTabWidth(int tabs) {
        byte[] buffer = Converter.wcsToMbcs(" ", true);
        long layout = GTK.gtk_widget_create_pango_layout(handle, buffer);
        int[] width = new int[1];
        int[] height = new int[1];
        OS.pango_layout_get_size(layout, width, height);
        OS.g_object_unref(layout);
        return width[0] * tabs;
    }

    /**
     * Returns the widget text.
     * <p>
     * The text for a text widget is the characters in the widget, or
     * an empty string if this has never been set.
     * </p>
     *
     * @return the widget text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public String getText() {
        return new String(getTextChars());
    }

    /**
     * Returns a range of text.  Returns an empty string if the
     * start of the range is greater than the end.
     * <p>
     * Indexing is zero based.  The range of
     * a selection is from 0..N-1 where N is
     * the number of characters in the widget.
     * </p>
     *
     * @param start the start of the range
     * @param end the end of the range
     * @return the range of text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public String getText(int start, int end) {
        checkWidget();
        if (!(start <= end && 0 <= end))
            return "";
        String str = getText();
        int length = str.length();
        end = Math.min(end, length - 1);
        if (start > end)
            return "";
        start = Math.max(0, start);
        /*
        * NOTE: The current implementation uses substring ()
        * which can reference a potentially large character
        * array.
        */
        return str.substring(start, end + 1);
    }

    /**
     * Returns the widget's text as a character array.
     * <p>
     * The text for a text widget is the characters in the widget, or
     * a zero-length array if this has never been set.
     * </p>
     * <p>
     * Note: Use this API to prevent the text from being written into a String
     * object whose lifecycle is outside of your control. This can help protect
     * the text, for example, when the widget is used as a password field.
     * However, the text can't be protected if an {@link SWT#Segments} or
     * {@link SWT#Verify} listener has been added to the widget.
     * </p>
     *
     * @return a character array that contains the widget's text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setTextChars(char[])
     *
     * @since 3.7
     */
    public char[] getTextChars() {
        checkWidget();
        long address;
        if ((style & SWT.SINGLE) != 0) {
            address = GTK.gtk_entry_get_text(handle);
        } else {
            byte[] start = new byte[ITER_SIZEOF];
            byte[] end = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_bounds(bufferHandle, start, end);
            address = GTK.gtk_text_buffer_get_text(bufferHandle, start, end, true);
        }
        if (address == 0)
            return new char[0];
        int length = C.strlen(address);
        byte[] buffer = new byte[length];
        C.memmove(buffer, address, length);
        if ((style & SWT.MULTI) != 0)
            OS.g_free(address);

        char[] result = Converter.mbcsToWcs(buffer);
        Arrays.fill(buffer, (byte) 0);
        if (segments != null) {
            result = deprocessText(result, 0, -1);
        }
        return result;
    }

    /**
     * Returns the maximum number of characters that the receiver is capable of holding.
     * <p>
     * If this has not been changed by <code>setTextLimit()</code>,
     * it will be the constant <code>Text.LIMIT</code>.
     * </p>
     *
     * @return the text limit
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #LIMIT
     */
    public int getTextLimit() {
        checkWidget();
        if ((style & SWT.MULTI) != 0)
            return LIMIT;
        int limit = GTK.gtk_entry_get_max_length(handle);
        return limit == 0 ? 0xFFFF : untranslateOffset(limit);
    }

    /**
     * Returns the zero-relative index of the line which is currently
     * at the top of the receiver.
     * <p>
     * This index can change when lines are scrolled or new lines are added or removed.
     * </p>
     *
     * @return the index of the top line
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getTopIndex() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0)
            return 0;
        byte[] position = new byte[ITER_SIZEOF];
        /*
         * Feature in GTK: GtkTextView widgets are subject to line validation
         * which happens during idle. This causes GtkTextIter to not update quickly
         * enough when changes are added to the text buffer. The fix is to use a
         * GtkTextMark to track the precise index, then convert it back to a
         * GtkTextIter when getTopIndex() is called. See bug 487467.
         *
         * NOTE: to cover cases where getTopIndex() is called without setTopIndex()
         * being called, we fetch the current GtkAdjustment value and cache it for
         * comparison. In getTopIndex() we compare the current value with the cached
         * one to see if the user has scrolled/moved the viewport using the GUI.
         * If so, we use the old method of fetching the top index.
         */
        long vAdjustment = GTK.gtk_scrollable_get_vadjustment(handle);
        currentAdjustment = GTK.gtk_adjustment_get_value(vAdjustment);
        if (cachedAdjustment == currentAdjustment) {
            // If indexMark is 0, fetch topIndex using the old method
            if (indexMark != 0) {
                GTK.gtk_text_buffer_get_iter_at_mark(bufferHandle, position, indexMark);
                return GTK.gtk_text_iter_get_line(position);
            }
        }
        GdkRectangle rect = new GdkRectangle();
        GTK.gtk_text_view_get_visible_rect(handle, rect);
        GTK.gtk_text_view_get_line_at_y(handle, position, rect.y, null);
        return GTK.gtk_text_iter_get_line(position);
    }

    /**
     * Returns the top SWT logical point.
     * <p>
     * The top point is the SWT logical point position of the line
     * that is currently at the top of the widget.  On
     * some platforms, a text widget can be scrolled by
     * points instead of lines so that a partial line
     * is displayed at the top of the widget.
     * </p><p>
     * The top SWT logical point changes when the widget is scrolled.
     * The top SWT logical point does not include the widget trimming.
     * </p>
     *
     * @return the SWT logical point position of the top line
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getTopPixel() {
        checkWidget();
        return DPIUtil.autoScaleDown(getTopPixelInPixels());
    }

    int getTopPixelInPixels() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0)
            return 0;
        byte[] position = new byte[ITER_SIZEOF];
        GdkRectangle rect = new GdkRectangle();
        GTK.gtk_text_view_get_visible_rect(handle, rect);
        int[] lineTop = new int[1];
        GTK.gtk_text_view_get_line_at_y(handle, position, rect.y, lineTop);
        return lineTop[0];
    }

    @Override
    long gtk_activate(long widget) {
        sendSelectionEvent(SWT.DefaultSelection);
        return 0;
    }

    @Override
    long gtk_button_press_event(long widget, long event) {
        long result;
        result = super.gtk_button_press_event(widget, event);
        if (result != 0)
            return result;
        int eventType = GDK.gdk_event_get_event_type(event);
        if (!doubleClick) {
            switch (eventType) {
            case GDK.GDK_2BUTTON_PRESS:
            case GDK.GDK_3BUTTON_PRESS:
                return 1;
            }
        }
        return result;
    }

    @Override
    long gtk_changed(long widget) {
        /*
        * Feature in GTK.  When the user types, GTK positions
        * the caret after sending the changed signal.  This
        * means that application code that attempts to position
        * the caret during a changed signal will fail.  The fix
        * is to post the modify event when the user is typing.
        */
        boolean keyPress = false;
        long eventPtr = GTK.gtk_get_current_event();
        if (eventPtr != 0) {
            int eventType = GDK.gdk_event_get_event_type(eventPtr);
            eventType = fixGdkEventTypeValues(eventType);
            switch (eventType) {
            case GDK.GDK_KEY_PRESS:
                keyPress = true;
                break;
            }
            gdk_event_free(eventPtr);
        }
        if (keyPress) {
            postEvent(SWT.Modify);
        } else {
            sendEvent(SWT.Modify);
        }
        if ((style & SWT.SEARCH) != 0) {
            if ((style & SWT.ICON_CANCEL) == 0) {
                // Default GtkSearchEntry shows "clear" icon when there is text, manually revert this
                // when "cancel" icon style is not set
                GTK.gtk_entry_set_icon_from_icon_name(handle, GTK.GTK_ENTRY_ICON_SECONDARY, null);
            }
        }
        return 0;
    }

    @Override
    long gtk_commit(long imContext, long text) {
        if (text == 0)
            return 0;
        if ((style & SWT.SINGLE) != 0) {
            if (!GTK.gtk_editable_get_editable(handle))
                return 0;
        }
        int length = C.strlen(text);
        if (length == 0)
            return 0;
        byte[] buffer = new byte[length];
        C.memmove(buffer, text, length);
        char[] chars = Converter.mbcsToWcs(buffer);
        Arrays.fill(buffer, (byte) 0);
        char[] newChars = sendIMKeyEvent(SWT.KeyDown, 0, chars);
        if (newChars == null)
            return 0;
        /*
        * Feature in GTK.  For a GtkEntry, during the insert-text signal,
        * GTK allows the programmer to change only the caret location,
        * not the selection.  If the programmer changes the selection,
        * the new selection is lost.  The fix is to detect a selection
        * change and set it after the insert-text signal has completed.
        */
        fixStart = fixEnd = -1;
        OS.g_signal_handlers_block_matched(imContext, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, COMMIT);
        int id = OS.g_signal_lookup(OS.commit, GTK.gtk_im_context_get_type());
        int mask = OS.G_SIGNAL_MATCH_DATA | OS.G_SIGNAL_MATCH_ID;
        OS.g_signal_handlers_unblock_matched(imContext, mask, id, 0, 0, 0, handle);
        if (newChars == chars) {
            OS.g_signal_emit_by_name(imContext, OS.commit, text);
        } else {
            buffer = Converter.wcsToMbcs(newChars, true);
            OS.g_signal_emit_by_name(imContext, OS.commit, buffer);
            Arrays.fill(buffer, (byte) 0);
        }
        OS.g_signal_handlers_unblock_matched(imContext, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, COMMIT);
        OS.g_signal_handlers_block_matched(imContext, mask, id, 0, 0, 0, handle);
        if ((style & SWT.SINGLE) != 0) {
            if (fixStart != -1 && fixEnd != -1) {
                GTK.gtk_editable_set_position(handle, fixStart);
                GTK.gtk_editable_select_region(handle, fixStart, fixEnd);
            }
        }
        fixStart = fixEnd = -1;
        return 0;
    }

    @Override
    long gtk_delete_range(long widget, long iter1, long iter2) {
        if (!hooks(SWT.Verify) && !filters(SWT.Verify))
            return 0;
        byte[] startIter = new byte[ITER_SIZEOF];
        byte[] endIter = new byte[ITER_SIZEOF];
        C.memmove(startIter, iter1, startIter.length);
        C.memmove(endIter, iter2, endIter.length);
        int start = GTK.gtk_text_iter_get_offset(startIter);
        int end = GTK.gtk_text_iter_get_offset(endIter);
        byte[] zero = new byte[ITER_SIZEOF];
        GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, zero, 0);
        long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, zero, endIter, true);
        start = (int) OS.g_utf8_offset_to_utf16_offset(ptr, start);
        end = (int) OS.g_utf8_offset_to_utf16_offset(ptr, end);
        OS.g_free(ptr);
        String newText = verifyText("", start, end);
        if (newText == null) {
            /* Remember the selection when the text was deleted */
            GTK.gtk_text_buffer_get_selection_bounds(bufferHandle, startIter, endIter);
            start = GTK.gtk_text_iter_get_offset(startIter);
            end = GTK.gtk_text_iter_get_offset(endIter);
            if (start != end) {
                fixStart = start;
                fixEnd = end;
            }
            OS.g_signal_stop_emission_by_name(bufferHandle, OS.delete_range);
        } else {
            if (newText.length() > 0) {
                byte[] buffer = Converter.wcsToMbcs(newText, false);
                OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
                OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_RANGE);
                GTK.gtk_text_buffer_delete(bufferHandle, startIter, endIter);
                OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                        DELETE_RANGE);
                OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
                OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                        TEXT_BUFFER_INSERT_TEXT);
                GTK.gtk_text_buffer_insert(bufferHandle, startIter, buffer, buffer.length);
                OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                        TEXT_BUFFER_INSERT_TEXT);
                OS.g_signal_stop_emission_by_name(bufferHandle, OS.delete_range);
                Arrays.fill(buffer, (byte) 0);
            }
        }
        return 0;
    }

    @Override
    long gtk_delete_text(long widget, long start_pos, long end_pos) {
        if (!hooks(SWT.Verify) && !filters(SWT.Verify))
            return 0;
        long ptr = GTK.gtk_entry_get_text(handle);
        if (end_pos == -1)
            end_pos = OS.g_utf8_strlen(ptr, -1);
        int start = (int) OS.g_utf8_offset_to_utf16_offset(ptr, start_pos);
        int end = (int) OS.g_utf8_offset_to_utf16_offset(ptr, end_pos);
        String newText = verifyText("", start, end);
        if (newText == null) {
            /* Remember the selection when the text was deleted */
            int[] newStart = new int[1], newEnd = new int[1];
            GTK.gtk_editable_get_selection_bounds(handle, newStart, newEnd);
            if (newStart[0] != newEnd[0]) {
                fixStart = newStart[0];
                fixEnd = newEnd[0];
            }
            OS.g_signal_stop_emission_by_name(handle, OS.delete_text);
        } else {
            if (newText.length() > 0) {
                int[] pos = new int[1];
                pos[0] = (int) end_pos;
                byte[] buffer = Converter.wcsToMbcs(newText, false);
                OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
                OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
                GTK.gtk_editable_insert_text(handle, buffer, buffer.length, pos);
                OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
                OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
                GTK.gtk_editable_set_position(handle, pos[0]);
                Arrays.fill(buffer, (byte) 0);
            }
        }
        return 0;
    }

    @Override
    long gtk_event_after(long widget, long gdkEvent) {
        if (cursor != null)
            setCursor(cursor.handle);
        /*
        * Feature in GTK.  The gtk-entry-select-on-focus property is a global
        * setting.  Return it to its default value after the GtkEntry has done
        * its focus in processing so that other widgets (such as the combo)
        * use the correct value.
        */
        if ((style & SWT.SINGLE) != 0 && display.entrySelectOnFocus) {
            int eventType = GDK.gdk_event_get_event_type(gdkEvent);
            eventType = fixGdkEventTypeValues(eventType);
            switch (eventType) {
            case GDK.GDK_FOCUS_CHANGE:
                boolean[] focusIn = new boolean[1];
                if (GTK.GTK4) {
                    GDK.gdk_event_get_focus_in(gdkEvent, focusIn);
                } else {
                    GdkEventFocus gdkEventFocus = new GdkEventFocus();
                    OS.memmove(gdkEventFocus, gdkEvent, GdkEventFocus.sizeof);
                    focusIn[0] = gdkEventFocus.in != 0;
                }
                if (focusIn[0]) {
                    long settings = GTK.gtk_settings_get_default();
                    OS.g_object_set(settings, GTK.gtk_entry_select_on_focus, true, 0);
                }
                break;
            }
        }
        return super.gtk_event_after(widget, gdkEvent);
    }

    @Override
    long gtk_draw(long widget, long cairo) {
        if ((state & OBSCURED) != 0)
            return 0;
        long result = super.gtk_draw(widget, cairo);
        return result;
    }

    @Override
    boolean mustBeVisibleOnInitBounds() {
        // Bug 542940: Workaround to avoid NPE, make Text visible on initialization
        return true;
    }

    @Override
    long gtk_focus_out_event(long widget, long event) {
        fixIM();
        return super.gtk_focus_out_event(widget, event);
    }

    @Override
    long gtk_grab_focus(long widget) {
        long result = super.gtk_grab_focus(widget);
        /*
        * Feature in GTK.  GtkEntry widgets select their text on focus in,
        * clearing the previous selection.  This behavior is controlled by
        * the gtk-entry-select-on-focus property.  The fix is to disable
        * this property when a GtkEntry is given focus and restore it after
        * the entry has done focus in processing.
        */
        if ((style & SWT.SINGLE) != 0 && display.entrySelectOnFocus) {
            long settings = GTK.gtk_settings_get_default();
            OS.g_object_set(settings, GTK.gtk_entry_select_on_focus, false, 0);
        }
        return result;
    }

    @Override
    long gtk_icon_release(long widget, long icon_pos, long event) {
        Event e = new Event();
        if (icon_pos == GTK.GTK_ENTRY_ICON_PRIMARY) {
            e.detail = SWT.ICON_SEARCH;
        } else {
            e.detail = SWT.ICON_CANCEL;
        }
        sendSelectionEvent(SWT.DefaultSelection, e, false);
        return 0;
    }

    @Override
    long gtk_insert_text(long widget, long new_text, long new_text_length, long position) {
        if (!hooks(SWT.Verify) && !filters(SWT.Verify))
            return 0;
        if (new_text == 0 || new_text_length == 0)
            return 0;
        byte[] buffer = new byte[(int) new_text_length];
        C.memmove(buffer, new_text, buffer.length);
        String oldText = new String(Converter.mbcsToWcs(buffer));
        int[] pos = new int[1];
        C.memmove(pos, position, 4);
        long ptr = GTK.gtk_entry_get_text(handle);
        if (pos[0] == -1)
            pos[0] = (int) OS.g_utf8_strlen(ptr, -1);
        /* Use the selection when the text was deleted */
        int start = pos[0], end = pos[0];
        if (fixStart != -1 && fixEnd != -1) {
            start = pos[0] = fixStart;
            end = fixEnd;
            fixStart = fixEnd = -1;
        }
        start = (int) OS.g_utf8_offset_to_utf16_offset(ptr, start);
        end = (int) OS.g_utf8_offset_to_utf16_offset(ptr, end);
        String newText = verifyText(oldText, start, end);
        if (newText != oldText && handle != 0) {
            int[] newStart = new int[1], newEnd = new int[1];
            GTK.gtk_editable_get_selection_bounds(handle, newStart, newEnd);
            if (newText != null) {
                if (newStart[0] != newEnd[0]) {
                    OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_TEXT);
                    OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
                    GTK.gtk_editable_delete_selection(handle);
                    OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_TEXT);
                    OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
                }
                byte[] buffer3 = Converter.wcsToMbcs(newText, false);
                OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
                GTK.gtk_editable_insert_text(handle, buffer3, buffer3.length, pos);
                OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
                newStart[0] = newEnd[0] = pos[0];
            }
            pos[0] = newEnd[0];
            if (newStart[0] != newEnd[0]) {
                fixStart = newStart[0];
                fixEnd = newEnd[0];
            }
            C.memmove(position, pos, 4);
            OS.g_signal_stop_emission_by_name(handle, OS.insert_text);
        }
        return 0;
    }

    @Override
    long gtk_key_press_event(long widget, long event) {
        boolean handleSegments = false, segmentsCleared = false;
        if (hooks(SWT.Segments) || filters(SWT.Segments)) {
            int length;
            int[] state = new int[1];
            GDK.gdk_event_get_state(event, state);
            if (GTK.GTK4) {
                long[] eventString = new long[1];
                GDK.gdk_event_get_string(event, eventString);
                length = (int) OS.g_utf16_strlen(eventString[0], -1);
            } else {
                GdkEventKey gdkEvent = new GdkEventKey();
                OS.memmove(gdkEvent, event, GdkEventKey.sizeof);
                length = gdkEvent.length;
            }
            if (length > 0 && (state[0] & (GDK.GDK_MOD1_MASK | GDK.GDK_CONTROL_MASK)) == 0) {
                handleSegments = true;
                if (segments != null) {
                    clearSegments(true);
                    segmentsCleared = true;
                }
            }
        }
        long result = super.gtk_key_press_event(widget, event);
        if (result != 0)
            fixIM();
        if (gdkEventKey == -1)
            result = 1;
        gdkEventKey = 0;
        if (handleSegments && (result != 0 || segmentsCleared)) {
            applySegments();
        }
        return result;
    }

    @Override
    long gtk_populate_popup(long widget, long menu) {
        if ((style & SWT.RIGHT_TO_LEFT) != 0) {
            GTK.gtk_widget_set_direction(menu, GTK.GTK_TEXT_DIR_RTL);
            GTK.gtk_container_forall(menu, display.setDirectionProc, GTK.GTK_TEXT_DIR_RTL);
        }
        return 0;
    }

    @Override
    long gtk_text_buffer_insert_text(long widget, long iter, long text, long length) {
        if (!hooks(SWT.Verify) && !filters(SWT.Verify))
            return 0;
        byte[] position = new byte[ITER_SIZEOF];
        C.memmove(position, iter, position.length);
        /* Use the selection when the text was deleted */
        int start = GTK.gtk_text_iter_get_offset(position), end = start;
        if (fixStart != -1 && fixEnd != -1) {
            start = fixStart;
            end = fixEnd;
            fixStart = fixEnd = -1;
        }
        byte[] zero = new byte[ITER_SIZEOF];
        GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, zero, 0);
        long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, zero, position, true);
        start = (int) OS.g_utf8_offset_to_utf16_offset(ptr, start);
        end = (int) OS.g_utf8_offset_to_utf16_offset(ptr, end);
        OS.g_free(ptr);
        byte[] buffer = new byte[(int) length];
        C.memmove(buffer, text, buffer.length);
        String oldText = new String(Converter.mbcsToWcs(buffer));
        String newText = verifyText(oldText, start, end);
        if (newText == null) {
            OS.g_signal_stop_emission_by_name(bufferHandle, OS.insert_text);
        } else {
            if (newText != oldText) {
                byte[] buffer1 = Converter.wcsToMbcs(newText, false);
                OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                        TEXT_BUFFER_INSERT_TEXT);
                GTK.gtk_text_buffer_insert(bufferHandle, iter, buffer1, buffer1.length);
                OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                        TEXT_BUFFER_INSERT_TEXT);
                OS.g_signal_stop_emission_by_name(bufferHandle, OS.insert_text);
            }
        }
        return 0;
    }

    @Override
    void hookEvents() {
        super.hookEvents();
        if ((style & SWT.SINGLE) != 0) {
            OS.g_signal_connect_closure(handle, OS.changed, display.getClosure(CHANGED), true);
            OS.g_signal_connect_closure(handle, OS.insert_text, display.getClosure(INSERT_TEXT), false);
            OS.g_signal_connect_closure(handle, OS.delete_text, display.getClosure(DELETE_TEXT), false);
            OS.g_signal_connect_closure(handle, OS.activate, display.getClosure(ACTIVATE), false);
            OS.g_signal_connect_closure(handle, OS.grab_focus, display.getClosure(GRAB_FOCUS), false);
            OS.g_signal_connect_closure(handle, OS.populate_popup, display.getClosure(POPULATE_POPUP), false);
            if ((style & SWT.SEARCH) != 0) {
                OS.g_signal_connect_closure(handle, OS.icon_release, display.getClosure(ICON_RELEASE), false);
            }
        } else {
            OS.g_signal_connect_closure(bufferHandle, OS.changed, display.getClosure(CHANGED), false);
            OS.g_signal_connect_closure(bufferHandle, OS.insert_text, display.getClosure(TEXT_BUFFER_INSERT_TEXT),
                    false);
            OS.g_signal_connect_closure(bufferHandle, OS.delete_range, display.getClosure(DELETE_RANGE), false);
            OS.g_signal_connect_closure(handle, OS.populate_popup, display.getClosure(POPULATE_POPUP), false);
        }
        long imContext = imContext();
        if (imContext != 0) {
            OS.g_signal_connect_closure(imContext, OS.commit, display.getClosure(COMMIT), false);
            int id = OS.g_signal_lookup(OS.commit, GTK.gtk_im_context_get_type());
            int mask = OS.G_SIGNAL_MATCH_DATA | OS.G_SIGNAL_MATCH_ID;
            OS.g_signal_handlers_block_matched(imContext, mask, id, 0, 0, 0, handle);
        }
        OS.g_signal_connect_closure(handle, OS.backspace, display.getClosure(BACKSPACE), false);
        OS.g_signal_connect_closure(handle, OS.backspace, display.getClosure(BACKSPACE_INVERSE), true);
        OS.g_signal_connect_closure(handle, OS.copy_clipboard, display.getClosure(COPY_CLIPBOARD), false);
        OS.g_signal_connect_closure(handle, OS.copy_clipboard, display.getClosure(COPY_CLIPBOARD_INVERSE), true);
        OS.g_signal_connect_closure(handle, OS.cut_clipboard, display.getClosure(CUT_CLIPBOARD), false);
        OS.g_signal_connect_closure(handle, OS.cut_clipboard, display.getClosure(CUT_CLIPBOARD_INVERSE), true);
        OS.g_signal_connect_closure(handle, OS.paste_clipboard, display.getClosure(PASTE_CLIPBOARD), false);
        OS.g_signal_connect_closure(handle, OS.paste_clipboard, display.getClosure(PASTE_CLIPBOARD_INVERSE), true);
        OS.g_signal_connect_closure(handle, OS.delete_from_cursor, display.getClosure(DELETE_FROM_CURSOR), false);
        OS.g_signal_connect_closure(handle, OS.delete_from_cursor, display.getClosure(DELETE_FROM_CURSOR_INVERSE),
                true);
        OS.g_signal_connect_closure(handle, OS.move_cursor, display.getClosure(MOVE_CURSOR), false);
        OS.g_signal_connect_closure(handle, OS.move_cursor, display.getClosure(MOVE_CURSOR_INVERSE), true);
        OS.g_signal_connect_closure(handle, OS.direction_changed, display.getClosure(DIRECTION_CHANGED), true);
    }

    long imContext() {
        if (imContext != 0)
            return imContext;
        return 0;
    }

    private boolean insideBlockSelection(int x, int y) {
        int start = 0, end = 0;
        if ((style & SWT.SINGLE) != 0) {
            int[] s = new int[1], e = new int[1];
            GTK.gtk_editable_get_selection_bounds(handle, s, e);
            start = s[0];
            end = e[0];
        } else {
            byte[] s = new byte[ITER_SIZEOF], e = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_selection_bounds(bufferHandle, s, e);
            start = GTK.gtk_text_iter_get_offset(s);
            end = GTK.gtk_text_iter_get_offset(e);
        }
        if (start != end) {
            if (end < start) {
                int temp = end;
                end = start;
                start = temp;
            }
            int position = -1;
            if ((style & SWT.SINGLE) != 0) {
                int[] index = new int[1];
                int[] trailing = new int[1];
                long layout = GTK.gtk_entry_get_layout(handle);
                OS.pango_layout_xy_to_index(layout, x * OS.PANGO_SCALE, y * OS.PANGO_SCALE, index, trailing);
                long ptr = OS.pango_layout_get_text(layout);
                position = (int) OS.g_utf8_pointer_to_offset(ptr, ptr + index[0]) + trailing[0];
            } else {
                byte[] p = new byte[ITER_SIZEOF];
                GTK.gtk_text_view_get_iter_at_location(handle, p, x, y);
                position = GTK.gtk_text_iter_get_offset(p);
            }
            if (start <= position && position < end) {
                return true;
            }
        }
        return false;
    }

    /**
     * Inserts a string.
     * <p>
     * The old selection is replaced with the new text.
     * </p>
     *
     * @param string the string
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string is <code>null</code></li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void insert(String string) {
        checkWidget();
        if (string == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        clearSegments(true);
        byte[] buffer = Converter.wcsToMbcs(string, false);
        if ((style & SWT.SINGLE) != 0) {
            int[] start = new int[1], end = new int[1];
            GTK.gtk_editable_get_selection_bounds(handle, start, end);
            GTK.gtk_editable_delete_selection(handle);
            GTK.gtk_editable_insert_text(handle, buffer, buffer.length, start);
            GTK.gtk_editable_set_position(handle, start[0]);
        } else {
            byte[] start = new byte[ITER_SIZEOF];
            byte[] end = new byte[ITER_SIZEOF];
            if (GTK.gtk_text_buffer_get_selection_bounds(bufferHandle, start, end)) {
                GTK.gtk_text_buffer_delete(bufferHandle, start, end);
            }
            GTK.gtk_text_buffer_insert(bufferHandle, start, buffer, buffer.length);
            GTK.gtk_text_buffer_place_cursor(bufferHandle, start);
            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
        GTK.gtk_text_view_get_visible_rect(handle, rect);
        lineHeight = getLineHeight();
        numLinesVisible = rect.height / lineHeight;

        // Get the coordinates of the insertion/selection point
        GTK.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) {
            GTK.gtk_text_buffer_get_iter_at_mark(bufferHandle, indexIter, indexMark);
            GTK.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 mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
                    GTK.gtk_text_view_scroll_to_mark(handle, mark, 0, true, 0, 0);
                } else if (scrollTo != null) {
                    GTK.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) {
                    GTK.gtk_text_view_scroll_to_iter(handle, scrollTo, 0, true, 0, 0);
                } else if (insert) {
                    long mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
                    GTK.gtk_text_view_scroll_to_mark(handle, mark, 0, true, 0, 0);
                }
            }
        }
    }

    @Override
    long paintWindow() {
        if ((style & SWT.SINGLE) != 0) {
            long window = super.paintWindow();
            long children = GDK.gdk_window_get_children(window);
            if (children != 0) {
                /*
                * When search or cancel icons are added to Text, those
                * icon window(s) are added to the beginning of the list.
                * In order to always return the correct window for Text,
                * browse to the end of the list.
                */
                do {
                    window = OS.g_list_data(children);
                } while ((children = OS.g_list_next(children)) != 0);
            }
            OS.g_list_free(children);
            return window;
        }
        GTK.gtk_widget_realize(handle);
        // TODO: this function has been removed on GTK4
        return GTK.gtk_text_view_get_window(handle, GTK.GTK_TEXT_WINDOW_TEXT);
    }

    @Override
    long paintSurface() {
        if ((style & SWT.SINGLE) != 0) {
            long surface = super.paintSurface();
            long children = GDK.gdk_surface_get_children(surface);
            if (children != 0) {
                /*
                * When search or cancel icons are added to Text, those
                * icon window(s) are added to the beginning of the list.
                * In order to always return the correct window for Text,
                * browse to the end of the list.
                */
                do {
                    surface = OS.g_list_data(children);
                } while ((children = OS.g_list_next(children)) != 0);
            }
            OS.g_list_free(children);
            return surface;
        }
        return 0;
    }

    /**
     * Pastes text from clipboard.
     * <p>
     * The selected text is deleted from the widget
     * and new text inserted from the clipboard.
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void paste() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_editable_paste_clipboard(handle);
        } else {
            long clipboard = GTK.GTK4 ? GDK.gdk_display_get_clipboard(GDK.gdk_display_get_default())
                    : GTK.gtk_clipboard_get(GDK.GDK_NONE);
            ;
            clearSegments(true);
            GTK.gtk_text_buffer_paste_clipboard(bufferHandle, clipboard, null,
                    GTK.gtk_text_view_get_editable(handle));
            applySegments();
        }
    }

    @Override
    void register() {
        super.register();
        if (bufferHandle != 0)
            display.addWidget(bufferHandle, this);
        long imContext = imContext();
        if (imContext != 0)
            display.addWidget(imContext, this);
    }

    @Override
    void releaseWidget() {
        super.releaseWidget();
        fixIM();
        message = null;
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the receiver's text is modified.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see ModifyListener
     * @see #addModifyListener
     */
    public void removeModifyListener(ModifyListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        if (eventTable == null)
            return;
        eventTable.unhook(SWT.Modify, listener);
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the receiver's text is modified.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SegmentEvent
     * @see SegmentListener
     * @see #addSegmentListener
     *
     * @since 3.8
     */
    public void removeSegmentListener(SegmentListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        eventTable.unhook(SWT.Segments, listener);
        clearSegments(true);
        applySegments();
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the control is selected by the user.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #addSelectionListener
     */
    public void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        if (eventTable == null)
            return;
        eventTable.unhook(SWT.Selection, listener);
        eventTable.unhook(SWT.DefaultSelection, listener);
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the control is verified.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see VerifyListener
     * @see #addVerifyListener
     */
    public void removeVerifyListener(VerifyListener listener) {
        checkWidget();
        if (listener == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        if (eventTable == null)
            return;
        eventTable.unhook(SWT.Verify, listener);
    }

    /**
     * Selects all the text in the receiver.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void selectAll() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_editable_select_region(handle, 0, -1);
        } else {
            byte[] start = new byte[ITER_SIZEOF];
            byte[] end = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, start, 0);
            GTK.gtk_text_buffer_get_end_iter(bufferHandle, end);
            GTK.gtk_text_buffer_select_range(bufferHandle, start, end);
        }
    }

    @Override
    GdkRGBA getContextBackgroundGdkRGBA() {
        if (background != null && (state & BACKGROUND) != 0) {
            return background;
        }
        return defaultBackground();
    }

    @Override
    GdkRGBA getContextColorGdkRGBA() {
        if (GTK.GTK_VERSION >= OS.VERSION(3, 14, 0)) {
            if (foreground != null) {
                return foreground;
            } else {
                return display.COLOR_WIDGET_FOREGROUND_RGBA;
            }
        } else {
            return super.getContextColorGdkRGBA();
        }
    }

    @Override
    void setBackgroundGdkRGBA(long context, long handle, GdkRGBA rgba) {
        /* Setting the background color overrides the selected background color.
         * To prevent this, we need to re-set the default. This can be done with CSS
         * on GTK3.16+, or by using GtkStateFlags as an argument to
         * gtk_widget_override_background_color() on versions of GTK3 less than 3.16.
         */
        if (rgba == null) {
            background = defaultBackground();
        } else {
            background = rgba;
        }
        GdkRGBA selectedBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION).handle;
        GdkRGBA selectedForeground = display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT).handle;
        String css;
        String properties;
        String name;
        String selection = GTK.GTK_VERSION >= OS.VERSION(3, 20, 0) ? " selection" : ":selected";
        if ((style & SWT.SINGLE) != 0) {
            name = GTK.GTK_VERSION >= OS.VERSION(3, 20, 0) ? "entry" : "GtkEntry";
            properties = " {background: " + display.gtk_rgba_to_css_string(background) + ";}\n" + name
                    + ":selected {background-color: " + display.gtk_rgba_to_css_string(selectedBackground) + ";}\n"
                    + name + selection + " {color: " + display.gtk_rgba_to_css_string(selectedForeground) + ";}";
        } else {
            name = GTK.GTK_VERSION >= OS.VERSION(3, 20, 0) ? "textview text" : "GtkTextView";
            properties = " {background-color: " + display.gtk_rgba_to_css_string(background) + ";}\n" + name
                    + ":selected {background-color: " + display.gtk_rgba_to_css_string(selectedBackground) + ";}\n"
                    + name + selection + " {color: " + display.gtk_rgba_to_css_string(selectedForeground) + ";}";
        }
        css = name + properties;

        // Cache background color
        cssBackground = css;

        // Apply background color and any foreground color
        String finalCss = display.gtk_css_create_css_color_string(cssBackground, cssForeground, SWT.BACKGROUND);
        gtk_css_provider_load_from_css(context, finalCss);
    }

    @Override
    void setForegroundGdkRGBA(GdkRGBA rgba) {
        if (GTK.GTK_VERSION >= OS.VERSION(3, 14, 0)) {
            foreground = rgba;
            GdkRGBA toSet = rgba == null ? display.COLOR_WIDGET_FOREGROUND_RGBA : rgba;
            setForegroundGdkRGBA(handle, toSet);
        } else {
            super.setForegroundGdkRGBA(rgba);
        }
    }

    @Override
    void setCursor(long cursor) {
        long defaultCursor = 0;
        if (cursor == 0) {
            if (GTK.GTK4) {
                byte[] name = Converter.wcsToMbcs("text", true);
                defaultCursor = GDK.gdk_cursor_new_from_name(name, 0);
            } else {
                defaultCursor = GDK.gdk_cursor_new_for_display(GDK.gdk_display_get_default(), GDK.GDK_XTERM);
            }
        }
        super.setCursor(cursor != 0 ? cursor : defaultCursor);
        if (cursor == 0)
            OS.g_object_unref(defaultCursor);
    }

    /**
     * Sets the double click enabled flag.
     * <p>
     * The double click flag enables or disables the
     * default action of the text widget when the user
     * double clicks.
     * </p><p>
     * Note: This operation is a hint and is not supported on
     * platforms that do not have this concept.
     * </p>
     *
     * @param doubleClick the new double click flag
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setDoubleClickEnabled(boolean doubleClick) {
        checkWidget();
        this.doubleClick = doubleClick;
    }

    /**
     * Sets the echo character.
     * <p>
     * The echo character is the character that is
     * displayed when the user enters text or the
     * text is changed by the programmer. Setting
     * the echo character to '\0' clears the echo
     * character and redraws the original text.
     * If for any reason the echo character is invalid,
     * or if the platform does not allow modification
     * of the echo character, the default echo character
     * for the platform is used.
     * </p>
     *
     * @param echo the new echo character
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setEchoChar(char echo) {
        checkWidget();
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_entry_set_visibility(handle, echo == '\0');
            GTK.gtk_entry_set_invisible_char(handle, echo);
        }
    }

    /**
     * Sets the editable state.
     *
     * @param editable the new editable state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setEditable(boolean editable) {
        checkWidget();
        style &= ~SWT.READ_ONLY;
        if (!editable)
            style |= SWT.READ_ONLY;
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_editable_set_editable(handle, editable);
        } else {
            GTK.gtk_text_view_set_editable(handle, editable);
        }
    }

    @Override
    void setFontDescription(long font) {
        super.setFontDescription(font);
        setTabStops(tabs);
    }

    /**
     * Sets the widget message. The message text is displayed
     * as a hint for the user, indicating the purpose of the field.
     * <p>
     * Typically this is used in conjunction with <code>SWT.SEARCH</code>.
     * </p>
     *
     * @param message the new message
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the message is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.3
     */
    public void setMessage(String message) {
        checkWidget();
        if (message == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        this.message = message;
        if ((style & SWT.SINGLE) != 0) {
            byte[] buffer = Converter.wcsToMbcs(message, true);
            GTK.gtk_entry_set_placeholder_text(handle, buffer);
            return;
        }
        redraw(false);
    }

    /**
     * Sets the orientation of the receiver, which must be one
     * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
     * <p>
     * Note: This operation is a hint and is not supported on
     * platforms that do not have this concept.
     * </p>
     *
     * @param orientation new orientation style
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 2.1.2
     */
    @Override
    public void setOrientation(int orientation) {
        checkWidget();
    }

    /**
     * Sets the selection.
     * <p>
     * Indexing is zero based.  The range of
     * a selection is from 0..N where N is
     * the number of characters in the widget.
     * </p><p>
     * Text selections are specified in terms of
     * caret positions.  In a text widget that
     * contains N characters, there are N+1 caret
     * positions, ranging from 0..N.  This differs
     * from other functions that address character
     * position such as getText () that use the
     * regular array indexing rules.
     * </p>
     *
     * @param start new caret position
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelection(int start) {
        checkWidget();
        start = translateOffset(start);
        if ((style & SWT.SINGLE) != 0) {
            long ptr = GTK.gtk_entry_get_text(handle);
            start = (int) OS.g_utf16_offset_to_utf8_offset(ptr, start);
            GTK.gtk_editable_set_position(handle, start);
        } else {
            byte[] startIter = new byte[ITER_SIZEOF];
            byte[] endIter = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_bounds(bufferHandle, startIter, endIter);
            long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, startIter, endIter, true);
            start = (int) OS.g_utf16_offset_to_utf8_offset(ptr, start);
            OS.g_free(ptr);
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, startIter, start);
            GTK.gtk_text_buffer_place_cursor(bufferHandle, startIter);
            scrollIfNotVisible(startIter, startIter, false);
        }
    }

    /**
     * Sets the selection to the range specified
     * by the given start and end indices.
     * <p>
     * Indexing is zero based.  The range of
     * a selection is from 0..N where N is
     * the number of characters in the widget.
     * </p><p>
     * Text selections are specified in terms of
     * caret positions.  In a text widget that
     * contains N characters, there are N+1 caret
     * positions, ranging from 0..N.  This differs
     * from other functions that address character
     * position such as getText () that use the
     * usual array indexing rules.
     * </p>
     *
     * @param start the start of the range
     * @param end the end of the range
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelection(int start, int end) {
        checkWidget();
        start = translateOffset(start);
        end = translateOffset(end);
        if ((style & SWT.SINGLE) != 0) {
            long ptr = GTK.gtk_entry_get_text(handle);
            start = (int) OS.g_utf16_offset_to_utf8_offset(ptr, start);
            end = (int) OS.g_utf16_offset_to_utf8_offset(ptr, end);
            GTK.gtk_editable_set_position(handle, start);
            GTK.gtk_editable_select_region(handle, start, end);
        } else {
            byte[] startIter = new byte[ITER_SIZEOF];
            byte[] endIter = new byte[ITER_SIZEOF];
            GTK.gtk_text_buffer_get_bounds(bufferHandle, startIter, endIter);
            long ptr = GTK.gtk_text_buffer_get_text(bufferHandle, startIter, endIter, true);
            start = (int) OS.g_utf16_offset_to_utf8_offset(ptr, start);
            end = (int) OS.g_utf16_offset_to_utf8_offset(ptr, end);
            OS.g_free(ptr);
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, startIter, start);
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, endIter, end);
            scrollIfNotVisible(startIter, startIter, false);
            GTK.gtk_text_buffer_select_range(bufferHandle, startIter, endIter);
        }
    }

    /**
     * Sets the selection to the range specified
     * by the given point, where the x coordinate
     * represents the start index and the y coordinate
     * represents the end index.
     * <p>
     * Indexing is zero based.  The range of
     * a selection is from 0..N where N is
     * the number of characters in the widget.
     * </p><p>
     * Text selections are specified in terms of
     * caret positions.  In a text widget that
     * contains N characters, there are N+1 caret
     * positions, ranging from 0..N.  This differs
     * from other functions that address character
     * position such as getText () that use the
     * usual array indexing rules.
     * </p>
     *
     * @param selection the point
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelection(Point selection) {
        checkWidget();
        if (selection == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        setSelection(selection.x, selection.y);
    }

    /**
     * Sets the number of tabs.
     * <p>
     * Tab stop spacing is specified in terms of the
     * space (' ') character.  The width of a single
     * tab stop is the pixel width of the spaces.
     * </p>
     *
     * @param tabs the number of tabs
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setTabs(int tabs) {
        checkWidget();
        if (tabs < 0)
            return;
        setTabStops(this.tabs = tabs);
    }

    void setTabStops(int tabs) {
        int tabWidth = getTabWidth(tabs);
        long tabArray = OS.pango_tab_array_new(1, false);
        OS.pango_tab_array_set_tab(tabArray, 0, OS.PANGO_TAB_LEFT, tabWidth);
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_entry_set_tabs(handle, tabArray);
        } else {
            GTK.gtk_text_view_set_tabs(handle, tabArray);
        }
        OS.pango_tab_array_free(tabArray);
    }

    /**
     * Sets the contents of the receiver to the given string. If the receiver has style
     * SINGLE and the argument contains multiple lines of text, the result of this
     * operation is undefined and may vary from platform to platform.
     * <p>
     * Note: If control characters like '\n', '\t' etc. are used
     * in the string, then the behavior is platform dependent.
     * </p>
     * @param string the new text
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setText(String string) {
        checkWidget();
        if (string == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        /*
        * Feature in gtk.  When text is set in gtk, separate events are fired for the deletion and
        * insertion of the text.  This is not wrong, but is inconsistent with other platforms.  The
        * fix is to block the firing of these events and fire them ourselves in a consistent manner.
        */
        if (hooks(SWT.Verify) || filters(SWT.Verify)) {
            string = verifyText(string, 0, getCharCount());
            if (string == null)
                return;
        }
        char[] text = new char[string.length()];
        string.getChars(0, text.length, text, 0);
        setText(text);
    }

    /**
     * Sets the contents of the receiver to the characters in the array. If the receiver
     * has style <code>SWT.SINGLE</code> and the argument contains multiple lines of text
     * then the result of this operation is undefined and may vary between platforms.
     * <p>
     * Note: Use this API to prevent the text from being written into a String
     * object whose lifecycle is outside of your control. This can help protect
     * the text, for example, when the widget is used as a password field.
     * However, the text can't be protected if an {@link SWT#Segments} or
     * {@link SWT#Verify} listener has been added to the widget.
     * </p>
     *
     * @param text a character array that contains the new text
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the array is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getTextChars()
     *
     * @since 3.7
     */
    public void setTextChars(char[] text) {
        checkWidget();
        if (text == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        /*
        * Feature in gtk.  When text is set in gtk, separate events are fired for the deletion and
        * insertion of the text.  This is not wrong, but is inconsistent with other platforms.  The
        * fix is to block the firing of these events and fire them ourselves in a consistent manner.
        */
        if (hooks(SWT.Verify) || filters(SWT.Verify)) {
            String string = verifyText(new String(text), 0, getCharCount());
            if (string == null)
                return;
            text = new char[string.length()];
            string.getChars(0, text.length, text, 0);
        }
        setText(text);
    }

    void setText(char[] text) {
        clearSegments(false);
        if ((style & SWT.SINGLE) != 0) {
            byte[] buffer = Converter.wcsToMbcs(text, true);
            OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_TEXT);
            OS.g_signal_handlers_block_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
            GTK.gtk_entry_set_text(handle, buffer);
            OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_TEXT);
            OS.g_signal_handlers_unblock_matched(handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, INSERT_TEXT);
            Arrays.fill(buffer, (byte) 0);
        } else {
            byte[] buffer = Converter.wcsToMbcs(text, false);
            byte[] position = new byte[ITER_SIZEOF];
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_RANGE);
            OS.g_signal_handlers_block_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                    TEXT_BUFFER_INSERT_TEXT);
            GTK.gtk_text_buffer_set_text(bufferHandle, buffer, buffer.length);
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, DELETE_RANGE);
            OS.g_signal_handlers_unblock_matched(bufferHandle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0,
                    TEXT_BUFFER_INSERT_TEXT);
            GTK.gtk_text_buffer_get_iter_at_offset(bufferHandle, position, 0);
            GTK.gtk_text_buffer_place_cursor(bufferHandle, position);
            long mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
            GTK.gtk_text_view_scroll_to_mark(handle, mark, 0, true, 0, 0);
            Arrays.fill(buffer, (byte) 0);
        }
        sendEvent(SWT.Modify);
        if ((style & SWT.SEARCH) != 0) {
            if ((style & SWT.ICON_CANCEL) == 0) {
                GTK.gtk_entry_set_icon_from_icon_name(handle, GTK.GTK_ENTRY_ICON_SECONDARY, null);
            }
        }
        applySegments();
    }

    /**
     * Sets the maximum number of characters that the receiver
     * is capable of holding to be the argument.
     * <p>
     * Instead of trying to set the text limit to zero, consider
     * creating a read-only text widget.
     * </p><p>
     * To reset this value to the default, use <code>setTextLimit(Text.LIMIT)</code>.
     * Specifying a limit value larger than <code>Text.LIMIT</code> sets the
     * receiver's limit to <code>Text.LIMIT</code>.
     * </p>
     *
     * @param limit new text limit
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_CANNOT_BE_ZERO - if the limit is zero</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #LIMIT
     */
    public void setTextLimit(int limit) {
        checkWidget();
        if (limit == 0)
            error(SWT.ERROR_CANNOT_BE_ZERO);
        if ((style & SWT.SINGLE) != 0) {
            GTK.gtk_entry_set_max_length(handle,
                    segments != null ? Math.min(LIMIT, translateOffset(limit)) : limit);
        }
    }

    /**
     * Sets the zero-relative index of the line which is currently
     * at the top of the receiver. This index can change when lines
     * are scrolled or new lines are added and removed.
     *
     * @param index the index of the top item
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setTopIndex(int index) {
        checkWidget();
        if ((style & SWT.SINGLE) != 0)
            return;
        byte[] position = new byte[ITER_SIZEOF];
        GTK.gtk_text_buffer_get_iter_at_line(bufferHandle, position, index);
        /*
         * Feature in GTK: create a new GtkTextMark for the purposes of keeping track of the top index. In getTopIndex() we
         * can use this without worrying about line validation. See bug 487467.
         *
         * We also cache the current GtkAdjustment value for future comparison in getTopIndex().
         */
        byte[] buffer = Converter.wcsToMbcs("index_mark", true);
        indexMark = GTK.gtk_text_buffer_create_mark(bufferHandle, buffer, position, true);
        GTK.gtk_text_view_scroll_to_mark(handle, indexMark, 0, true, 0, 0);
        long vAdjustment = GTK.gtk_scrollable_get_vadjustment(handle);
        cachedAdjustment = GTK.gtk_adjustment_get_value(vAdjustment);
    }

    /**
     * Shows the selection.
     * <p>
     * If the selection is already showing
     * in the receiver, this method simply returns.  Otherwise,
     * lines are scrolled until the selection is visible.
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void showSelection() {
        checkWidget();
        if ((style & SWT.SINGLE) != 0)
            return;
        long mark = GTK.gtk_text_buffer_get_selection_bound(bufferHandle);
        GTK.gtk_text_view_scroll_to_mark(handle, mark, 0, true, 0, 0);
        mark = GTK.gtk_text_buffer_get_insert(bufferHandle);
        GTK.gtk_text_view_scroll_to_mark(handle, mark, 0, true, 0, 0);
    }

    int translateOffset(int offset) {
        if (segments == null)
            return offset;
        for (int i = 0, nSegments = segments.length; i < nSegments && offset - i >= segments[i]; i++) {
            offset++;
        }
        return offset;
    }

    @Override
    boolean translateTraversal(long event) {
        int[] key = new int[1];
        GDK.gdk_event_get_keyval(event, key);
        switch (key[0]) {
        case GDK.GDK_KP_Enter:
        case GDK.GDK_Return: {
            long imContext = imContext();
            if (imContext != 0) {
                long[] preeditString = new long[1];
                GTK.gtk_im_context_get_preedit_string(imContext, preeditString, null, null);
                if (preeditString[0] != 0) {
                    int length = C.strlen(preeditString[0]);
                    OS.g_free(preeditString[0]);
                    if (length != 0)
                        return false;
                }
            }
        }
        }
        return super.translateTraversal(event);
    }

    @Override
    int traversalCode(int key, long event) {
        int bits = super.traversalCode(key, event);
        if ((style & SWT.READ_ONLY) != 0)
            return bits;
        if ((style & SWT.MULTI) != 0) {
            bits &= ~SWT.TRAVERSE_RETURN;
            if (key == GDK.GDK_Tab && event != 0) {
                int[] eventState = new int[1];
                GDK.gdk_event_get_state(event, eventState);
                boolean next = (eventState[0] & GDK.GDK_SHIFT_MASK) == 0;
                if (next && (eventState[0] & GDK.GDK_CONTROL_MASK) == 0) {
                    bits &= ~(SWT.TRAVERSE_TAB_NEXT | SWT.TRAVERSE_TAB_PREVIOUS);
                }
            }
        }
        return bits;
    }

    int untranslateOffset(int offset) {
        if (segments == null)
            return offset;
        for (int i = 0, nSegments = segments.length; i < nSegments && offset > segments[i]; i++) {
            offset--;
        }
        return offset;
    }

    String verifyText(String string, int start, int end) {
        if (string != null && string.length() == 0 && start == end)
            return null;
        Event event = new Event();
        event.text = string;
        event.start = start;
        event.end = end;
        long eventPtr = GTK.gtk_get_current_event();
        if (eventPtr != 0) {
            int type = GDK.gdk_event_get_event_type(eventPtr);
            type = fixGdkEventTypeValues(type);
            switch (type) {
            case GDK.GDK_KEY_PRESS:
                setKeyState(event, eventPtr);
                break;
            }
            gdk_event_free(eventPtr);
        }
        /*
         * It is possible (but unlikely), that application
         * code could have disposed the widget in the verify
         * event.  If this happens, answer null to cancel
         * the operation.
         */
        sendEvent(SWT.Verify, event);
        if (!event.doit || isDisposed())
            return null;
        return event.text;
    }

    @Override
    long windowProc(long handle, long user_data) {
        if (hooks(SWT.Segments) || filters(SWT.Segments) || segments != null) {
            switch ((int) user_data) {
            case BACKSPACE:
            case COPY_CLIPBOARD:
            case CUT_CLIPBOARD:
            case PASTE_CLIPBOARD: {
                clearSegments(true);
                break;
            }
            case BACKSPACE_INVERSE:
            case COPY_CLIPBOARD_INVERSE:
            case CUT_CLIPBOARD_INVERSE:
            case PASTE_CLIPBOARD_INVERSE: {
                applySegments();
                break;
            }
            }
        }
        return super.windowProc(handle, user_data);
    }

    @Override
    long windowProc(long handle, long arg0, long user_data) {
        if (hooks(SWT.Segments) || filters(SWT.Segments) || segments != null) {
            switch ((int) user_data) {
            case DIRECTION_CHANGED: {
                clearSegments(true);
                applySegments();
                break;
            }
            }
        }
        return super.windowProc(handle, arg0, user_data);
    }

    @Override
    long windowProc(long handle, long arg0, long arg1, long user_data) {
        if (hooks(SWT.Segments) || filters(SWT.Segments) || segments != null) {
            switch ((int) user_data) {
            case DELETE_FROM_CURSOR: {
                clearSegments(true);
                break;
            }
            case DELETE_FROM_CURSOR_INVERSE: {
                applySegments();
                break;
            }
            }
        }
        return super.windowProc(handle, arg0, arg1, user_data);
    }

    @Override
    long windowProc(long handle, long arg0, long arg1, long arg2, long user_data) {
        if (hooks(SWT.Segments) || filters(SWT.Segments) || segments != null) {
            switch ((int) user_data) {
            case MOVE_CURSOR: {
                if (arg0 == GTK.GTK_MOVEMENT_VISUAL_POSITIONS) {
                    clearSegments(true);
                }
                break;
            }
            case MOVE_CURSOR_INVERSE: {
                if (arg0 == GTK.GTK_MOVEMENT_VISUAL_POSITIONS) {
                    applySegments();
                }
                break;
            }
            }
        }
        return super.windowProc(handle, arg0, arg1, arg2, user_data);
    }

}