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

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.widgets.DateTime.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
 *     Red Hat         - GtkSpinButton rewrite. 2014.10.09
 *     Lablicate GmbH  - add locale support/improve editing support for date/time styles. 2017.02.08
 *******************************************************************************/
package org.eclipse.swt.widgets;

import java.text.*;
import java.text.AttributedCharacterIterator.*;
import java.text.DateFormat.*;
import java.util.*;

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

/*
 * Developer note: Unit tests for this class can be found under:
 * org.eclipse.swt.tests.junit.Test_org_eclipse_swt_widgets_DateTime
 */

/**
 * Instances of this class are selectable user interface
 * objects that allow the user to enter and modify date
 * or time values.
 * <p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to add children to it, or set a layout on it.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG, DROP_DOWN, CALENDAR_WEEKNUMBERS</dd>
 * <dt><b>Events:</b></dt>
 * <dd>DefaultSelection, Selection</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified,
 * and only one of the styles SHORT, MEDIUM, or LONG may be specified.
 * The DROP_DOWN style is only valid with the DATE style.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @see <a href="http://www.eclipse.org/swt/snippets/#datetime">DateTime 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>
 *
 * @since 3.3
 * @noextend This class is not intended to be subclassed by clients.
 */
public class DateTime extends Composite {
    int day, month, year, hours, minutes, seconds;

    /**
     * Major handles of this class.
     * Note, these can vary or all equal each other depending on Date/Time/Calendar/Drop_down
     * configuration used. See createHandle () */
    long textEntryHandle, spinButtonHandle, containerHandle, calendarHandle;

    /* Emulated DATE and TIME fields */
    Calendar calendar;
    Button down;
    FieldPosition currentField;
    StringBuilder typeBuffer = new StringBuilder();
    int typeBufferPos = -1;
    boolean firstTime = true;
    private DateFormat dateFormat;
    /* DROP_DOWN calendar fields for DATE */
    Color fg, bg;
    boolean hasFocus;
    int savedYear, savedMonth, savedDay;
    Shell popupShell;
    DateTime popupCalendar;
    Listener popupListener, popupFilter;

    Point prefferedSize;
    Locale locale;

    /** Used when SWT.DROP_DOWN is set */
    Listener mouseEventListener;

    /*
     * Used for easier access to format pattern of DATE and TIME.
     * See https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
     */
    static final String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yy";
    static final String DEFAULT_MEDIUM_DATE_FORMAT = "d-MMM-yyyy";
    static final String DEFAULT_LONG_DATE_FORMAT = "MMMM d, yyyy";
    static final String DEFAULT_SHORT_TIME_FORMAT = "h:mm a";
    static final String DEFAULT_MEDIUM_TIME_FORMAT = "h:mm:ss a";
    static final String DEFAULT_LONG_TIME_FORMAT = "h:mm:ss z a";
    static final int MIN_YEAR = 1752; // Gregorian switchover in North America: September 19, 1752
    static final int MAX_YEAR = 9999;
    static final int SPACE_FOR_CURSOR = 1;

    private int mdYear;

    private int mdMonth;

    /**
     * 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#DATE
     * @see SWT#TIME
     * @see SWT#CALENDAR
     * @see SWT#CALENDAR_WEEKNUMBERS
     * @see SWT#SHORT
     * @see SWT#MEDIUM
     * @see SWT#LONG
     * @see SWT#DROP_DOWN
     * @see Widget#checkSubclass
     * @see Widget#getStyle
     */
    public DateTime(Composite parent, int style) {
        super(parent, checkStyle(style));
        if (isDate() || isTime()) {
            createText();
        }

        if (isCalendar()) {
            GTK.gtk_calendar_mark_day(calendarHandle, Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
        }

        if (isDateWithDropDownButton()) {
            createDropDownButton();
            createPopupShell(-1, -1, -1);
            addListener(SWT.Resize, event -> setDropDownButtonSize());
        }
        initAccessible();

        if (isDateWithDropDownButton()) {
            //Date w/ drop down button is in containers.
            //first time round we set the bounds manually for correct Right_to_left behaviour
            Point size = computeSizeInPixels(SWT.DEFAULT, SWT.DEFAULT);
            setBoundsInPixels(0, 0, size.x, size.y);
        }
    }

    void createText() {
        String property = System.getProperty("swt.datetime.locale");
        if (property == null || property.isEmpty()) {
            locale = Locale.getDefault();
        } else {
            locale = Locale.forLanguageTag(property);
        }
        dateFormat = getFormat(locale, style);
        dateFormat.setLenient(false);
        calendar = Calendar.getInstance(locale);
        updateControl();
        selectField(updateField(currentField));
    }

    DateFormat getFormat(Locale locale, int style) {
        int dfStyle;
        if ((style & SWT.LONG) != 0) {
            dfStyle = DateFormat.LONG;
        } else if ((style & SWT.SHORT) != 0) {
            dfStyle = DateFormat.SHORT;
        } else {
            dfStyle = DateFormat.MEDIUM;
        }
        if (isDate()) {
            return DateFormat.getDateInstance(dfStyle, locale);
        } else if (isTime()) {
            return DateFormat.getTimeInstance(dfStyle, locale);
        } else {
            throw new IllegalStateException("can only be called for date or time widgets!");
        }
    }

    static int checkStyle(int style) {
        /*
        * Even though it is legal to create this widget
        * with scroll bars, they serve no useful purpose
        * because they do not automatically scroll the
        * widget's client area.  The fix is to clear
        * the SWT style.
        */
        style &= ~(SWT.H_SCROLL | SWT.V_SCROLL);

        style = checkBits(style, SWT.DATE, SWT.TIME, SWT.CALENDAR, 0, 0, 0);
        if ((style & SWT.DATE) == 0)
            style &= ~SWT.DROP_DOWN;
        return checkBits(style, SWT.MEDIUM, SWT.SHORT, SWT.LONG, 0, 0, 0);
    }

    /**
     * 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 called when the user changes the control's value.
     * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed.
     * </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 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);
    }

    @Override
    protected void checkSubclass() {
        if (!isValidSubclass())
            error(SWT.ERROR_INVALID_SUBCLASS);
    }

    /**
     * Compute the native text entry size when the formatted text inside the entry
     * is at the longest length possible. i.e. Assume DATE/HOUR field to be double digit,
     * MONTH field for SWT.DATE | SWT.LONG is the longest text.
     *
     * @param wHint
     * @param hHint
     * @param changed
     * @return text entry size to hold the longest possible formatted text.
     */
    Point computeMaxTextSize(int wHint, int hHint, boolean changed) {
        String currentText = getFormattedString();
        String formatPattern = getComputeSizeString(style);

        switch (formatPattern) {
        case DEFAULT_MEDIUM_DATE_FORMAT:
            // Make the DATE field a double digit
            String longDateText = currentText.replaceFirst("\\d{1,2}", "00");
            setText(longDateText);
            break;
        case DEFAULT_LONG_DATE_FORMAT:
            // Make the MONTH field the longest length possible, the DATE field a double digit.
            Set<String> months = calendar.getDisplayNames(Calendar.MONTH, Calendar.LONG, locale).keySet();
            String longestMonth = Collections.max(months, (s1, s2) -> s1.length() - s2.length()); // Probably September
            String doubleDigitDate = currentText.replaceFirst("\\d{1,2}", "00");
            String longText = doubleDigitDate.replaceFirst("[^\\s]+", longestMonth);
            setText(longText);
            break;
        case DEFAULT_SHORT_TIME_FORMAT:
        case DEFAULT_MEDIUM_TIME_FORMAT:
        case DEFAULT_LONG_TIME_FORMAT:
            // Make the HOUR field a double digit
            String longTimeText = currentText.replaceFirst("\\d{1,2}", "00");
            setText(longTimeText);
            break;
        default:
            // Fixed length for DEFAULT_SHORT_DATE_FORMAT, no need to adjust text length.
        }

        Point textSize = computeNativeSize(GTK.GTK4 ? spinButtonHandle : textEntryHandle, wHint, hHint, changed);
        // Change the text back to match the current calendar
        updateControl();
        return textSize;
    }

    @Override
    Point computeSizeInPixels(int wHint, int hHint, boolean changed) {
        checkWidget();

        int width = 0, height = 0;
        //For Date and Time, we cache the preffered size as there is no need to recompute it.
        if (!changed && (isDate() || isTime()) && prefferedSize != null) {
            width = (wHint != SWT.DEFAULT) ? wHint : prefferedSize.x;
            height = (hHint != SWT.DEFAULT) ? hHint : prefferedSize.y;
            return new Point(width, height);
        }

        if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
            if (isCalendar()) {
                Point size = computeNativeSize(containerHandle, wHint, hHint, changed);
                width = size.x;
                height = size.y;
            } else {
                /*
                 * Bug 538612: Computing the native size for textEntry when the current text
                 * is not the longest length possible causes sizing issues when the entry text
                 * is changed. Fix is to always allocate enough size to hold the longest possible
                 * formatted text.
                 */
                Point textSize = computeMaxTextSize(wHint, hHint, changed);
                Rectangle trim = computeTrimInPixels(0, 0, textSize.x, textSize.y);
                if (isDateWithDropDownButton()) {
                    Point buttonSize = down.computeSizeInPixels(SWT.DEFAULT, SWT.DEFAULT, changed);
                    width = trim.width + buttonSize.x;
                    height = Math.max(trim.height, buttonSize.y);
                } else if (isDate() || isTime()) {
                    width = trim.width;
                    height = trim.height;
                }
            }
        }
        if (width == 0)
            width = DEFAULT_WIDTH;
        if (height == 0)
            height = DEFAULT_HEIGHT;
        if (wHint != SWT.DEFAULT)
            width = wHint;
        if (hHint != SWT.DEFAULT)
            height = hHint;
        int borderWidth = getBorderWidthInPixels();

        if (prefferedSize == null && isDateWithDropDownButton()) {
            prefferedSize = new Point(width + 2 * borderWidth, height + 2 * borderWidth);
            return prefferedSize;
        } else {
            return new Point(width + 2 * borderWidth, height + 2 * borderWidth);
        }
    }

    @Override
    Rectangle computeTrimInPixels(int x, int y, int width, int height) {
        if (isCalendar()) {
            return super.computeTrimInPixels(x, y, width, height);
        }

        checkWidget();
        Rectangle trim = super.computeTrimInPixels(x, y, width, height);
        int xborder = 0, yborder = 0;
        GtkBorder tmp = new GtkBorder();
        long context = GTK.gtk_widget_get_style_context(GTK.GTK4 ? spinButtonHandle : textEntryHandle);
        int state_flag = GTK.GTK_VERSION < OS.VERSION(3, 18, 0) ? GTK.GTK_STATE_FLAG_NORMAL
                : GTK.gtk_widget_get_state_flags(textEntryHandle);
        gtk_style_context_get_padding(context, state_flag, tmp);
        trim.x -= tmp.left;
        trim.y -= tmp.top;
        trim.width += tmp.left + tmp.right;
        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(textEntryHandle);
            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;
        }
        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);
    }

    @Override
    void createHandle(int index) {
        createHandle();
    }

    /**
     * Here we carefully define the three internal handles:
     *  textEntryHandle
     *  containerHandle
     *  calendarHandle
     */
    void createHandle() {
        if (isCalendar()) {
            state |= HANDLE;
            createHandleForFixed();
            createHandleForCalendar();

        } else {
            createHandleForFixed();
            if (isDateWithDropDownButton()) {
                createHandleForDateWithDropDown();
            } else {
                createHandleForDateTime();
            }
            GTK.gtk_editable_set_editable(textEntryHandle, (style & SWT.READ_ONLY) == 0);
            if (GTK.GTK_VERSION <= OS.VERSION(3, 20, 0)) {
                GTK.gtk_entry_set_has_frame(textEntryHandle, (style & SWT.BORDER) != 0);
            }
        }
    }

    private void createHandleForFixed() {
        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);
    }

    private void createHandleForCalendar() {
        calendarHandle = GTK.gtk_calendar_new();
        if (calendarHandle == 0)
            error(SWT.ERROR_NO_HANDLES);

        //Calenadar becomes container in this case.
        handle = calendarHandle;
        containerHandle = calendarHandle;

        GTK.gtk_container_add(fixedHandle, calendarHandle);

        int flags = GTK.GTK_CALENDAR_SHOW_HEADING | GTK.GTK_CALENDAR_SHOW_DAY_NAMES;
        if (showWeekNumbers()) {
            flags |= GTK.GTK_CALENDAR_SHOW_WEEK_NUMBERS;
        }
        GTK.gtk_calendar_set_display_options(calendarHandle, flags);
        GTK.gtk_widget_show(calendarHandle);
    }

    private void createHandleForDateWithDropDown() {
        //Create box to put entry and button into box.
        containerHandle = gtk_box_new(GTK.GTK_ORIENTATION_HORIZONTAL, false, 0);
        if (containerHandle == 0)
            error(SWT.ERROR_NO_HANDLES);
        GTK.gtk_container_add(fixedHandle, containerHandle);

        //Create entry
        textEntryHandle = GTK.gtk_entry_new();
        if (textEntryHandle == 0)
            error(SWT.ERROR_NO_HANDLES);
        GTK.gtk_container_add(containerHandle, textEntryHandle);

        GTK.gtk_widget_show(containerHandle);
        GTK.gtk_widget_show(textEntryHandle);

        handle = containerHandle;
        if (handle == 0)
            error(SWT.ERROR_NO_HANDLES);

        // 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);
    }

    private void createHandleForDateTime() {
        long adjusment = GTK.gtk_adjustment_new(0, -9999, 9999, 1, 0, 0);
        if (GTK.GTK4) {
            spinButtonHandle = GTK.gtk_spin_button_new(adjusment, 1, 0);
            long boxHandle = GTK.gtk_widget_get_first_child(spinButtonHandle);
            long textHandle = GTK.gtk_widget_get_first_child(boxHandle);
            textEntryHandle = textHandle;
            handle = spinButtonHandle;
            containerHandle = spinButtonHandle;
        } else {
            textEntryHandle = GTK.gtk_spin_button_new(adjusment, 1, 0);
            handle = textEntryHandle;
            containerHandle = textEntryHandle;
        }
        if (textEntryHandle == 0)
            error(SWT.ERROR_NO_HANDLES);

        GTK.gtk_spin_button_set_numeric(GTK.GTK4 ? spinButtonHandle : textEntryHandle, false);
        GTK.gtk_container_add(fixedHandle, GTK.GTK4 ? spinButtonHandle : textEntryHandle);
        GTK.gtk_spin_button_set_wrap(GTK.GTK4 ? spinButtonHandle : textEntryHandle, (style & SWT.WRAP) != 0);
    }

    void createDropDownButton() {
        down = new Button(this, SWT.ARROW | SWT.DOWN);
        GTK.gtk_widget_set_can_focus(down.handle, false);
        down.addListener(SWT.Selection, event -> {
            setFocus();
            dropDownCalendar(!isDropped());
        });

        popupListener = event -> {
            if (event.widget == popupShell) {
                popupShellEvent(event);
                return;
            }
            if (event.widget == popupCalendar) {
                popupCalendarEvent(event);
                return;
            }
            if (event.widget == DateTime.this) {
                onDispose(event);
                return;
            }
            if (event.widget == getShell()) {
                getDisplay().asyncExec(() -> {
                    if (isDisposed())
                        return;
                    handleFocus(SWT.FocusOut);
                });
            }
        };
        popupFilter = event -> {
            Shell shell = ((Control) event.widget).getShell();
            if (shell == DateTime.this.getShell()) {
                handleFocus(SWT.FocusOut);
            }
        };
    }

    void createPopupShell(int year, int month, int day) {
        popupShell = new Shell(getShell(), SWT.NO_TRIM | SWT.ON_TOP);
        int popupStyle = SWT.CALENDAR;
        if (showWeekNumbers()) {
            popupStyle |= SWT.CALENDAR_WEEKNUMBERS;
        }
        popupCalendar = new DateTime(popupShell, popupStyle);
        if (font != null)
            popupCalendar.setFont(font);
        if (fg != null)
            popupCalendar.setForeground(fg);
        if (bg != null)
            popupCalendar.setBackground(bg);

        mouseEventListener = event -> {
            if (event.widget instanceof Control) {
                Control c = (Control) event.widget;
                if (c != down && c.getShell() != popupShell)
                    dropDownCalendar(false);
            }
        };

        int[] listeners = { SWT.Close, SWT.MouseUp };
        for (int i = 0; i < listeners.length; i++) {
            popupShell.addListener(listeners[i], popupListener);
        }
        listeners = new int[] { SWT.MouseDown, SWT.MouseUp, SWT.Selection, SWT.Traverse, SWT.KeyDown, SWT.KeyUp,
                SWT.FocusIn, SWT.FocusOut, SWT.Dispose };
        for (int i = 0; i < listeners.length; i++) {
            popupCalendar.addListener(listeners[i], popupListener);
        }
        addListener(SWT.Dispose, popupListener);
        if (year != -1)
            popupCalendar.setDate(year, month, day);
    }

    @Override
    void setFontDescription(long font) {
        if (isDateWithDropDownButton()) {
            prefferedSize = null; //flush cache for computeSize as font can cause size to change.
            setFontDescription(textEntryHandle, font);
        }
        super.setFontDescription(font);
    }

    @Override
    boolean checkSubwindow() {
        return false;
    }

    @Override
    void createWidget(int index) {
        super.createWidget(index);
        if (isCalendar()) {
            getDate();
        }
    }

    void onDispose(Event event) {
        if (popupShell != null && !popupShell.isDisposed()) {
            popupCalendar.removeListener(SWT.Dispose, popupListener);
            popupShell.dispose();
        }
        Shell shell = getShell();
        shell.removeListener(SWT.Deactivate, popupListener);
        Display display = getDisplay();
        display.removeFilter(SWT.FocusIn, popupFilter);
        popupShell = null;
        popupCalendar = null;
        down = null;
    }

    /**
     * Called when pressing the SWT.DROP_DOWN button on a Date Field
     * @param drop true if the calendar is suppose to drop down.
     */
    void dropDownCalendar(boolean drop) {
        if (drop == isDropped())
            return;

        if (!drop) {
            hideDropDownCalendar();
            return;
        }

        setCurrentDate();

        if (getShell() != popupShell.getParent()) {
            recreateCalendar();
        }

        //This is the x/y/width/height of the container of DateTime
        Point containerBounds = getSizeInPixels();
        Point calendarSize = popupCalendar.computeSizeInPixels(SWT.DEFAULT, SWT.DEFAULT, false);

        //Set the inner calendar pos/size. (not the popup shell pos/size)
        popupCalendar.setBoundsInPixels(1, 1, Math.max(containerBounds.x - 2, calendarSize.x), calendarSize.y);

        //Set Date & focus current day
        popupCalendar.setDate(savedYear, savedMonth, savedDay);
        focusDayOnPopupCalendar();

        Display display = getDisplay();

        //To display popup calendar, we need to know where the parent is relative to the whole screen.
        Rectangle coordsRelativeToScreen = display.mapInPixels(getParent(), null, getBoundsInPixels());
        Rectangle displayRect = DPIUtil.autoScaleUp(getMonitor().getClientArea());

        showPopupShell(containerBounds, calendarSize, coordsRelativeToScreen, displayRect);

        display.addFilter(SWT.MouseDown, mouseEventListener);
    }

    private void showPopupShell(Point containerBounds, Point calendarSize, Rectangle coordsRelativeToScreen,
            Rectangle displayRect) {
        int width = Math.max(containerBounds.x, calendarSize.x + 2);
        int height = calendarSize.y + 2;
        int y = calculateCalendarYpos(containerBounds, coordsRelativeToScreen, height, displayRect);
        int x = calculateCalendarXpos(calendarSize, coordsRelativeToScreen, displayRect, width);

        popupShell.setBoundsInPixels(x, y, width, height);
        popupShell.setVisible(true);
        if (isFocusControl()) {
            popupCalendar.setFocus();
        }
    }

    private int calculateCalendarYpos(Point containerBounds, Rectangle coordsRelativeToScreen, int height,
            Rectangle displayRect) {
        int dateEntryHeight = computeNativeSize(containerHandle, SWT.DEFAULT, SWT.DEFAULT, false).y;
        int y = coordsRelativeToScreen.y + containerBounds.y / 2 + dateEntryHeight / 2;

        //Put Calendar above control if it would be cut off at the bottom.
        if (y + height > displayRect.y + displayRect.height) {
            y -= (height + dateEntryHeight);
        }
        return y;
    }

    private int calculateCalendarXpos(Point calendarSize, Rectangle coordsRelativeToScreen, Rectangle displayRect,
            int width) {
        Integer x;
        x = coordsRelativeToScreen.x;
        //Move calendar to the right if it would be cut off.
        if (x + width > displayRect.x + displayRect.width) {
            x = displayRect.x + displayRect.width - calendarSize.x;
        }
        return x;
    }

    private void focusDayOnPopupCalendar() {
        int currentYear = Calendar.getInstance().get(Calendar.YEAR);
        int currentMonth = Calendar.getInstance().get(Calendar.MONTH);

        if (savedYear == currentYear && savedMonth == currentMonth) {
            int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
            GTK.gtk_calendar_mark_day(popupCalendar.handle, currentDay);
        }
    }

    private void setCurrentDate() {
        savedYear = getYear();
        savedMonth = getMonth();
        savedDay = getDay();
    }

    private void recreateCalendar() {
        int year = popupCalendar.getYear();
        int month = popupCalendar.getMonth();
        int day = popupCalendar.getDay();
        popupCalendar.removeListener(SWT.Dispose, popupListener);
        popupShell.dispose();
        popupShell = null;
        popupCalendar = null;
        createPopupShell(year, month, day);
    }

    private void hideDropDownCalendar() {
        popupShell.setVisible(false);
        GTK.gtk_calendar_clear_marks(popupCalendar.handle);
        display.removeFilter(SWT.MouseDown, mouseEventListener);
        return;
    }

    String getComputeSizeString(int style) {
        if ((style & SWT.DATE) != 0) {
            return (style & SWT.SHORT) != 0 ? DEFAULT_SHORT_DATE_FORMAT
                    : (style & SWT.LONG) != 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT;
        }
        // SWT.TIME
        return (style & SWT.SHORT) != 0 ? DEFAULT_SHORT_TIME_FORMAT
                : (style & SWT.LONG) != 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT;
    }

    String getFormattedString() {
        return dateFormat.format(calendar.getTime());
    }

    void getDate() {
        int[] y = new int[1];
        int[] m = new int[1];
        int[] d = new int[1];
        GTK.gtk_calendar_get_date(calendarHandle, y, m, d);
        year = y[0];
        month = m[0];
        day = d[0];
    }

    /**
     * Returns the receiver's date, or day of the month.
     * <p>
     * The first day of the month is 1, and the last day depends on the month and year.
     * </p>
     *
     * @return a positive integer beginning with 1
     *
     * @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 getDay() {
        checkWidget();
        if (isCalendar()) {
            getDate();
            return day;
        } else {
            return calendar.get(Calendar.DAY_OF_MONTH);
        }
    }

    /**
     * Returns the receiver's hours.
     * <p>
     * Hours is an integer between 0 and 23.
     * </p>
     *
     * @return an integer between 0 and 23
     *
     * @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 getHours() {
        checkWidget();
        if (isCalendar()) {
            return hours;
        } else {
            return calendar.get(Calendar.HOUR_OF_DAY);
        }
    }

    /**
     * Returns the receiver's minutes.
     * <p>
     * Minutes is an integer between 0 and 59.
     * </p>
     *
     * @return an integer between 0 and 59
     *
     * @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 getMinutes() {
        checkWidget();
        if (isCalendar()) {
            return minutes;
        } else {
            return calendar.get(Calendar.MINUTE);
        }
    }

    /**
     * Returns the receiver's month.
     * <p>
     * The first month of the year is 0, and the last month is 11.
     * </p>
     *
     * @return an integer between 0 and 11
     *
     * @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 getMonth() {
        checkWidget();
        if (isCalendar()) {
            getDate();
            return month;
        } else {
            return calendar.get(Calendar.MONTH);
        }
    }

    @Override
    String getNameText() {
        if (calendar == null) {
            return "";
        }
        if (isTime()) {
            return getHours() + ":" + getMinutes() + ":" + getSeconds();
        } else {
            return (getMonth() + 1) + "/" + getDay() + "/" + getYear();
        }
    }

    /**
     * Returns the receiver's seconds.
     * <p>
     * Seconds is an integer between 0 and 59.
     * </p>
     *
     * @return an integer between 0 and 59
     *
     * @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 getSeconds() {
        checkWidget();
        if (isCalendar()) {
            return seconds;
        } else {
            return calendar.get(Calendar.SECOND);
        }
    }

    /*
     * Returns a textual representation of the receiver,
     * intended for speaking the text aloud.
     */
    String getSpokenText() {
        if (isTime()) {
            return DateFormat.getTimeInstance(DateFormat.FULL).format(calendar.getTime());
        } else if (isDate()) {
            return DateFormat.getDateInstance(DateFormat.FULL).format(calendar.getTime());
        } else {
            Calendar cal = Calendar.getInstance();
            getDate();
            cal.set(year, month, day);
            return DateFormat.getDateInstance(DateFormat.FULL).format(cal.getTime());
        }
    }

    /**
     * Returns the receiver's year.
     * <p>
     * The first year is 1752 and the last year is 9999.
     * </p>
     *
     * @return an integer between 1752 and 9999
     *
     * @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 getYear() {
        checkWidget();
        if (isCalendar()) {
            getDate();
            return year;
        } else {
            return calendar.get(Calendar.YEAR);
        }
    }

    @Override
    long gtk_day_selected(long widget) {
        sendSelectionEvent();
        return 0;
    }

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

    @Override
    long gtk_month_changed(long widget) {
        sendSelectionEvent();
        return 0;
    }

    @Override
    long eventHandle() {
        return dateTimeHandle();
    }

    @Override
    long focusHandle() {
        return dateTimeHandle();
    }

    @Override
    long fontHandle() {
        return dateTimeHandle();
    }

    private long dateTimeHandle() {
        if (isCalendar() && calendarHandle != 0) {
            return calendarHandle;
        } else if ((isDate() || isTime())) {
            if (GTK.GTK4) {
                if (spinButtonHandle != 0)
                    return spinButtonHandle;
            } else {
                if (textEntryHandle != 0)
                    return textEntryHandle;
            }
            return super.focusHandle();
        } else {
            return super.focusHandle();
        }
    }

    @Override
    void hookEvents() {
        super.hookEvents();
        if (isCalendar()) {
            hookEventsForCalendar();
        } else {
            int eventMask = GDK.GDK_POINTER_MOTION_MASK | GDK.GDK_BUTTON_PRESS_MASK | GDK.GDK_BUTTON_RELEASE_MASK;
            GTK.gtk_widget_add_events(textEntryHandle, eventMask);

            if ((style & SWT.DROP_DOWN) == 0) {
                hookEventsForDateTimeSpinner();
            }
            if (OS.G_OBJECT_TYPE(textEntryHandle) == GTK.GTK_TYPE_MENU()) {
                hookEventsForMenu();
            }
        }
    }

    final private void hookEventsForCalendar() {
        OS.g_signal_connect_closure(calendarHandle, OS.day_selected, display.getClosure(DAY_SELECTED), false);
        OS.g_signal_connect_closure(calendarHandle, OS.day_selected_double_click,
                display.getClosure(DAY_SELECTED_DOUBLE_CLICK), false);
        OS.g_signal_connect_closure(calendarHandle, OS.month_changed, display.getClosure(MONTH_CHANGED), false);
    }

    final private void hookEventsForDateTimeSpinner() {
        OS.g_signal_connect_closure(textEntryHandle, OS.output, display.getClosure(OUTPUT), true);
        if (GTK.GTK4) {
            long keyController = GTK.gtk_event_controller_key_new();
            GTK.gtk_widget_add_controller(textEntryHandle, keyController);
            GTK.gtk_event_controller_set_propagation_phase(keyController, GTK.GTK_PHASE_TARGET);

            long focusAddress = display.focusCallback.getAddress();
            OS.g_signal_connect(keyController, OS.focus_in, focusAddress, FOCUS_IN);
        } else {
            OS.g_signal_connect_closure(textEntryHandle, OS.focus_in_event, display.getClosure(FOCUS_IN_EVENT),
                    true);
        }
    }

    final private void hookEventsForMenu() {
        OS.g_signal_connect_closure(down.handle, OS.selection_done, display.getClosure(SELECTION_DONE), true);
    }

    void incrementField(int amount) {
        if (currentField != null) {
            int field = getCalendarField(currentField);
            if (field == Calendar.HOUR && hasAmPm()) {
                int max = calendar.getMaximum(Calendar.HOUR);
                int min = calendar.getMinimum(Calendar.HOUR);
                int value = calendar.get(Calendar.HOUR);
                if ((value == max && amount == 1) || (value == min && amount == -1)) {
                    calendar.roll(Calendar.AM_PM, amount);
                }
            }
            if (field > -1) {
                calendar.roll(field, amount);
                updateControl();
                selectField(updateField(currentField));
            }
        }
    }

    private boolean hasAmPm() {
        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        while (iterator.current() != CharacterIterator.DONE) {
            for (Attribute attribute : iterator.getAttributes().keySet()) {
                if (Field.AM_PM.equals(attribute)) {
                    return true;
                }
            }
            iterator.setIndex(iterator.getRunLimit());
        }
        return false;
    }

    boolean isDropped() {
        return popupShell.getVisible();
    }

    private boolean isCalendar() {
        return ((style & SWT.CALENDAR) != 0);
    }

    private boolean isDateWithDropDownButton() {
        return ((style & SWT.DROP_DOWN) != 0 && (style & SWT.DATE) != 0);
    }

    private boolean isDate() {
        return ((style & SWT.DATE) != 0);
    }

    private boolean isTime() {
        return ((style & SWT.TIME) != 0);
    }

    private boolean isReadOnly() {
        return ((style & SWT.READ_ONLY) != 0);
    }

    private boolean showWeekNumbers() {
        return ((style & SWT.CALENDAR_WEEKNUMBERS) != 0);
    }

    void initAccessible() {
        Accessible accessible = getAccessible();
        accessible.addAccessibleListener(new AccessibleAdapter() {
            @Override
            public void getName(AccessibleEvent e) {
                e.result = getSpokenText();
            }

            @Override
            public void getHelp(AccessibleEvent e) {
                e.result = getToolTipText();
            }
        });

        accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
            @Override
            public void getChildAtPoint(AccessibleControlEvent e) {
                e.childID = ACC.CHILDID_SELF;
            }

            @Override
            public void getLocation(AccessibleControlEvent e) {
                Rectangle rect = display.map(getParent(), null, getBounds());
                e.x = rect.x;
                e.y = rect.y;
                e.width = rect.width;
                e.height = rect.height;
            }

            @Override
            public void getChildCount(AccessibleControlEvent e) {
                e.detail = 0;
            }

            @Override
            public void getRole(AccessibleControlEvent e) {
                e.detail = (isCalendar()) ? ACC.ROLE_LABEL : ACC.ROLE_TEXT;
            }

            @Override
            public void getState(AccessibleControlEvent e) {
                e.detail = ACC.STATE_FOCUSABLE;
                if (hasFocus())
                    e.detail |= ACC.STATE_FOCUSED;
            }

            @Override
            public void getSelection(AccessibleControlEvent e) {
                if (hasFocus())
                    e.childID = ACC.CHILDID_SELF;
            }

            @Override
            public void getFocus(AccessibleControlEvent e) {
                if (hasFocus())
                    e.childID = ACC.CHILDID_SELF;
            }
        });
    }

    boolean isValidTime(int fieldName, int value) {
        Calendar validCalendar;
        if (isCalendar()) {
            validCalendar = Calendar.getInstance();
        } else {
            validCalendar = calendar;
        }
        int min = validCalendar.getActualMinimum(fieldName);
        int max = validCalendar.getActualMaximum(fieldName);
        return value >= min && value <= max;
    }

    boolean isValidDate(int year, int month, int day) {
        if (year < MIN_YEAR || year > MAX_YEAR)
            return false;
        Calendar valid = Calendar.getInstance();
        valid.set(year, month, day);
        return valid.get(Calendar.YEAR) == year && valid.get(Calendar.MONTH) == month
                && valid.get(Calendar.DAY_OF_MONTH) == day;
    }

    void popupCalendarEvent(Event event) {
        switch (event.type) {
        case SWT.Dispose:
            if (popupShell != null && !popupShell.isDisposed() && !isDisposed()
                    && getShell() != popupShell.getParent()) {
                int year = popupCalendar.getYear();
                int month = popupCalendar.getMonth();
                int day = popupCalendar.getDay();
                popupShell = null;
                popupCalendar = null;
                createPopupShell(year, month, day);
            }
            break;
        case SWT.FocusIn: {
            handleFocus(SWT.FocusIn);
            break;
        }
        case SWT.MouseDown: {
            if (event.button != 1)
                return;
            mdYear = getYear();
            mdMonth = getMonth();
            break;
        }
        case SWT.MouseUp: {
            if (event.button != 1)
                return;
            /*
            * The drop-down should stay visible when
            * either the year or month is changed.
            */
            if (mdYear == getYear() && mdMonth == getMonth()) {
                dropDownCalendar(false);
            }
            break;
        }
        case SWT.Selection: {
            int year = popupCalendar.getYear();
            int month = popupCalendar.getMonth();
            int day = popupCalendar.getDay();
            setDate(year, month, day);
            Event e = new Event();
            e.time = event.time;
            e.stateMask = event.stateMask;
            e.doit = event.doit;
            notifyListeners(SWT.Selection, e);
            event.doit = e.doit;
            break;
        }
        case SWT.Traverse: {
            switch (event.detail) {
            case SWT.TRAVERSE_RETURN:
            case SWT.TRAVERSE_ESCAPE:
            case SWT.TRAVERSE_ARROW_PREVIOUS:
            case SWT.TRAVERSE_ARROW_NEXT:
                event.doit = false;
                break;
            case SWT.TRAVERSE_TAB_NEXT:
            case SWT.TRAVERSE_TAB_PREVIOUS:
                //               event.doit = text.traverse (event.detail);
                event.detail = SWT.TRAVERSE_NONE;
                if (event.doit)
                    dropDownCalendar(false);
                return;
            case SWT.TRAVERSE_PAGE_NEXT:
            case SWT.TRAVERSE_PAGE_PREVIOUS:
                return;
            }
            Event e = new Event();
            e.time = event.time;
            e.detail = event.detail;
            e.doit = event.doit;
            e.character = event.character;
            e.keyCode = event.keyCode;
            notifyListeners(SWT.Traverse, e);
            event.doit = e.doit;
            event.detail = e.detail;
            break;
        }
        case SWT.KeyUp: {
            Event e = new Event();
            e.time = event.time;
            e.character = event.character;
            e.keyCode = event.keyCode;
            e.stateMask = event.stateMask;
            notifyListeners(SWT.KeyUp, e);
            break;
        }
        case SWT.KeyDown: {
            if (event.character == SWT.ESC) {
                /* Escape key cancels popupCalendar and reverts date */
                popupCalendar.setDate(savedYear, savedMonth, savedDay);
                setDate(savedYear, savedMonth, savedDay);
                dropDownCalendar(false);
            }
            if (event.keyCode == SWT.CR || (event.stateMask & SWT.ALT) != 0
                    && (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN)) {
                /* Return, Alt+Down, and Alt+Up cancel popupCalendar and select date. */
                dropDownCalendar(false);
            }
            if (event.keyCode == SWT.SPACE) {
                dropDownCalendar(false);
            }
            /* At this point the widget may have been disposed.
             * If so, do not continue. */
            if (isDisposed())
                break;
            Event e = new Event();
            e.time = event.time;
            e.character = event.character;
            e.keyCode = event.keyCode;
            e.stateMask = event.stateMask;
            notifyListeners(SWT.KeyDown, e);
            break;
        }
        }
    }

    void handleFocus(int type) {
        if (isDisposed())
            return;
        switch (type) {
        case SWT.FocusIn: {
            if (hasFocus)
                return;
            selectAll();
            hasFocus = true;
            Shell shell = getShell();
            shell.removeListener(SWT.Deactivate, popupListener);
            shell.addListener(SWT.Deactivate, popupListener);
            Display display = getDisplay();
            display.removeFilter(SWT.FocusIn, popupFilter);
            display.addFilter(SWT.FocusIn, popupFilter);
            Event e = new Event();
            notifyListeners(SWT.FocusIn, e);
            break;
        }
        case SWT.FocusOut: {
            if (!hasFocus)
                return;
            Control focusControl = getDisplay().getFocusControl();
            if (focusControl == down || focusControl == popupCalendar)
                return;
            hasFocus = false;
            Shell shell = getShell();
            shell.removeListener(SWT.Deactivate, popupListener);
            Display display = getDisplay();
            display.removeFilter(SWT.FocusIn, popupFilter);
            display.removeFilter(SWT.MouseDown, mouseEventListener);
            Event e = new Event();
            notifyListeners(SWT.FocusOut, e);
            break;
        }
        }
    }

    void popupShellEvent(Event event) {
        switch (event.type) {
        case SWT.Close:
            event.doit = false;
            dropDownCalendar(false);
            break;
        case SWT.MouseUp:
            dropDownCalendar(false);
            break;
        }
    }

    /**
     * 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);
    }

    /**
     * selects the first occurrence of the given field
     *
     * @param field
     */
    void selectField(Field field) {
        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        while (iterator.current() != CharacterIterator.DONE) {
            for (Attribute attribute : iterator.getAttributes().keySet()) {
                if (attribute.equals(field)) {
                    selectField(getFieldPosition(field, iterator));
                    return;
                }
            }
            iterator.setIndex(iterator.getRunLimit());
        }
    }

    /**
     * Selects the given field at the given start/end coordinates
     *
     * @param field
     * @param start
     * @param end
     */
    void selectField(FieldPosition fieldPosition) {
        boolean sameField = isSameField(fieldPosition, currentField);
        if (sameField) {
            if (typeBufferPos > -1) {
                typeBufferPos = 0;
            }
        } else {
            typeBufferPos = -1;
            commitData();
            fieldPosition = updateField(fieldPosition);
        }
        Point pt = getSelection();
        int start = fieldPosition.getBeginIndex();
        int end = fieldPosition.getEndIndex();
        if (sameField && start == pt.x && end == pt.y) {
            return;
        }
        currentField = fieldPosition;
        display.syncExec(() -> {
            if (textEntryHandle != 0) {
                String value = getText(getText(), start, end - 1);
                int s = value.lastIndexOf(' ');
                s = (s == -1) ? start : start + s + 1;
                setSelection(s, end);
            }
        });
        sendSelectionEvent(SWT.Selection);
    }

    void sendSelectionEvent() {
        int[] y = new int[1];
        int[] m = new int[1];
        int[] d = new int[1];
        GTK.gtk_calendar_get_date(calendarHandle, y, m, d);
        //TODO: hours, minutes, seconds?
        if (d[0] != day || m[0] != month || y[0] != year) {
            year = y[0];
            month = m[0];
            day = d[0];
            /* Highlight the current (today) date */
            if (year == Calendar.getInstance().get(Calendar.YEAR)
                    && month == Calendar.getInstance().get(Calendar.MONTH)) {
                GTK.gtk_calendar_mark_day(calendarHandle, Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
            } else {
                GTK.gtk_calendar_clear_marks(calendarHandle);
            }
            sendSelectionEvent(SWT.Selection);
        }
    }

    @Override
    public void setBackground(Color color) {
        super.setBackground(color);
        bg = color;
        if (popupCalendar != null)
            popupCalendar.setBackground(color);
    }

    @Override
    void setBackgroundGdkRGBA(GdkRGBA rgba) {
        super.setBackgroundGdkRGBA(rgba);
        if (calendarHandle != 0) {
            setBackgroundGdkRGBA(calendarHandle, rgba);
        }
        super.setBackgroundGdkRGBA(rgba);

    }

    @Override
    void setBackgroundGdkRGBA(long context, long handle, GdkRGBA rgba) {
        // We need to override here because DateTime widgets use "background" instead of
        // "background-color" as their CSS property.
        if (GTK.GTK_VERSION >= OS.VERSION(3, 14, 0)) {
            // Form background string
            String name = GTK.GTK_VERSION >= OS.VERSION(3, 20, 0) ? display.gtk_widget_class_get_css_name(handle)
                    : display.gtk_widget_get_name(handle);
            String css = name + " {background: " + display.gtk_rgba_to_css_string(rgba) + ";}\n" + name
                    + ":selected" + " {background: "
                    + display.gtk_rgba_to_css_string(display.COLOR_LIST_SELECTION_RGBA) + ";}";

            // Cache background
            cssBackground = css;

            // Apply background color and any cached foreground color
            String finalCss = display.gtk_css_create_css_color_string(cssBackground, cssForeground, SWT.BACKGROUND);
            gtk_css_provider_load_from_css(context, finalCss);
        } else {
            super.setBackgroundGdkRGBA(context, handle, rgba);
        }
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (isDateWithDropDownButton())
            down.setEnabled(enabled);
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        this.font = font;
        if (popupCalendar != null)
            popupCalendar.setFont(font);
        redraw();
    }

    @Override
    void setForegroundGdkRGBA(GdkRGBA rgba) {
        setForegroundGdkRGBA(containerHandle, rgba);
    }

    @Override
    public void setForeground(Color color) {
        super.setForeground(color);
        fg = color;
        if (popupCalendar != null)
            popupCalendar.setForeground(color);
    }

    void setFieldOfInternalDataStructure(FieldPosition field, int value) {
        int calendarField = getCalendarField(field);
        if (calendar.get(calendarField) == value)
            return;
        if (calendarField == Calendar.AM_PM && hasAmPm()) {
            calendar.roll(Calendar.HOUR_OF_DAY, 12);
        }
        calendar.set(calendarField, value);

        //When dealing with months with 31 days and have days set to 31, then if you change the month
        //to one that has 30 (or less) days, then in calendar only the day is changed but the month stays.
        //e.g 10.31.2014  -> decrement month, becomes:
        //    10.01.2014.
        //To get around this behaviour, we set the field again.
        if (calendar.get(calendarField) != value) {
            calendar.set(calendarField, value);
        }
        sendSelectionEvent(SWT.Selection);
    }

    /**
     * Sets the receiver's year, month, and day in a single operation.
     * <p>
     * This is the recommended way to set the date, because setting the year,
     * month, and day separately may result in invalid intermediate dates.
     * </p>
     *
     * @param year an integer between 1752 and 9999
     * @param month an integer between 0 and 11
     * @param day a positive integer beginning with 1
     *
     * @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.4
     */
    public void setDate(int year, int month, int day) {
        checkWidget();
        if (!isValidDate(year, month, day))
            return;
        if (isCalendar()) {
            this.year = year;
            this.month = month;
            this.day = day;
            GTK.gtk_calendar_select_month(calendarHandle, month, year);
            GTK.gtk_calendar_select_day(calendarHandle, day);
        } else {
            calendar.set(year, month, day);
            updateControl();
        }
    }

    /**
     * Sets the receiver's date, or day of the month, to the specified day.
     * <p>
     * The first day of the month is 1, and the last day depends on the month and year.
     * If the specified day is not valid for the receiver's month and year, then it is ignored.
     * </p>
     *
     * @param day a positive integer beginning with 1
     *
     * @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 #setDate
     */
    public void setDay(int day) {
        checkWidget();
        if (!isValidDate(getYear(), getMonth(), day))
            return;
        if (isCalendar()) {
            this.day = day;
            GTK.gtk_calendar_select_day(calendarHandle, day);
        } else {
            calendar.set(Calendar.DAY_OF_MONTH, day);
            updateControl();
        }
    }

    /**
     * Sets the receiver's hours.
     * <p>
     * Hours is an integer between 0 and 23.
     * </p>
     *
     * @param hours an integer between 0 and 23
     *
     * @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 setHours(int hours) {
        checkWidget();
        if (!isValidTime(Calendar.HOUR_OF_DAY, hours))
            return;
        if (isCalendar()) {
            this.hours = hours;
        } else {
            calendar.set(Calendar.HOUR_OF_DAY, hours);
            updateControl();
        }
    }

    @Override
    public void setMenu(Menu menu) {
        super.setMenu(menu);
        if (down != null)
            down.setMenu(menu);
    }

    /**
     * Sets the receiver's minutes.
     * <p>
     * Minutes is an integer between 0 and 59.
     * </p>
     *
     * @param minutes an integer between 0 and 59
     *
     * @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 setMinutes(int minutes) {
        checkWidget();
        if (!isValidTime(Calendar.MINUTE, minutes))
            return;
        if (isCalendar()) {
            this.minutes = minutes;
        } else {
            calendar.set(Calendar.MINUTE, minutes);
            updateControl();
        }
    }

    /**
     * Sets the receiver's month.
     * <p>
     * The first month of the year is 0, and the last month is 11.
     * If the specified month is not valid for the receiver's day and year, then it is ignored.
     * </p>
     *
     * @param month an integer between 0 and 11
     *
     * @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 #setDate
     */
    public void setMonth(int month) {
        checkWidget();
        if (!isValidDate(getYear(), month, getDay()))
            return;
        if (isCalendar()) {
            this.month = month;
            GTK.gtk_calendar_select_month(calendarHandle, month, year);
        } else {
            calendar.set(Calendar.MONTH, month);
            updateControl();
        }
    }

    /**
     * Sets the receiver's seconds.
     * <p>
     * Seconds is an integer between 0 and 59.
     * </p>
     *
     * @param seconds an integer between 0 and 59
     *
     * @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 setSeconds(int seconds) {
        checkWidget();
        if (!isValidTime(Calendar.SECOND, seconds))
            return;
        if (isCalendar()) {
            this.seconds = seconds;
        } else {
            calendar.set(Calendar.SECOND, seconds);
            updateControl();
        }
    }

    /**
     * Sets the receiver's hours, minutes, and seconds in a single operation.
     *
     * @param hours an integer between 0 and 23
     * @param minutes an integer between 0 and 59
     * @param seconds an integer between 0 and 59
     *
     * @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.4
     */
    public void setTime(int hours, int minutes, int seconds) {
        checkWidget();
        if (!isValidTime(Calendar.HOUR_OF_DAY, hours))
            return;
        if (!isValidTime(Calendar.MINUTE, minutes))
            return;
        if (!isValidTime(Calendar.SECOND, seconds))
            return;
        if (isCalendar()) {
            this.hours = hours;
            this.minutes = minutes;
            this.seconds = seconds;
        } else {
            calendar.set(Calendar.HOUR_OF_DAY, hours);
            calendar.set(Calendar.MINUTE, minutes);
            calendar.set(Calendar.SECOND, seconds);
            updateControl();
        }
    }

    /**
     * Sets the receiver's year.
     * <p>
     * The first year is 1752 and the last year is 9999.
     * If the specified year is not valid for the receiver's day and month, then it is ignored.
     * </p>
     *
     * @param year an integer between 1752 and 9999
     *
     * @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 #setDate
     */
    public void setYear(int year) {
        checkWidget();
        if (!isValidDate(year, getMonth(), getDay()))
            return;
        if (isCalendar()) {
            this.year = year;
            GTK.gtk_calendar_select_month(calendarHandle, month, year);
        } else {
            calendar.set(Calendar.YEAR, year);
            updateControl();
        }
    }

    @Override
    void setBoundsInPixels(int x, int y, int width, int height) {

        //Date with Drop down is in container. Needs extra handling.
        if (isDateWithDropDownButton()) {
            long sizingHandle = GTK.GTK4 ? spinButtonHandle : textEntryHandle;
            GtkRequisition requisition = new GtkRequisition();
            GTK.gtk_widget_get_preferred_size(sizingHandle, null, requisition);
            int oldHeight = requisition.height; //Entry should not expand vertically. It is single liner.

            int newWidth = width - (down.getSizeInPixels().x + getGtkBorderPadding().right);
            GTK.gtk_widget_set_size_request(sizingHandle, (newWidth >= 0) ? newWidth : 0, oldHeight);
        }

        /*
         * TAG_GTK_CALENDAR_VERTICAL_FILL_WORKAROUND_394534
         * Work around a GtkCalendar bug in GTK3:
         * https://bugzilla.gnome.org/show_bug.cgi?id=737670
         *
         * In GTK3.0 - 3.14.2 (but not Gtk2) if the calendar is expanded beyond a certain size,
         * (e.g in the case of 'Vertical fill' in ControlExample, then the days shift down
         * and they become un-selectable. e.g, see screen shot:
         * https://bug737670.bugzilla-attachments.gnome.org/attachment.cgi?id=287470
         *
         * To work around this, if gtk 3.0 - 3.14.2 is used, do not allow the calendar to expand beyond it's preffered
         * native height.
         */
        int fixedGtkVersion = OS.VERSION(3, 14, 2);
        if (isCalendar() && (GTK.GTK_VERSION < fixedGtkVersion)) {
            int calendarPrefferedVerticalSize = computeSizeInPixels(SWT.DEFAULT, SWT.DEFAULT, true).y;
            if (height > calendarPrefferedVerticalSize) {
                height = calendarPrefferedVerticalSize;
            }
        }
        super.setBoundsInPixels(x, y, width, height);

    }

    /**
     * Usually called when control is resized or first initialized.
     */
    private void setDropDownButtonSize() {
        Rectangle rect = getClientAreaInPixels();
        int parentWidth = rect.width;
        int parentHeight = rect.height;
        Point buttonSize = down.computeSizeInPixels(SWT.DEFAULT, parentHeight);

        //TAG_GTK3__NO_VERTICAL_FILL_ADJUSTMENT
        int dateEntryHeight = computeNativeSize(GTK.GTK4 ? spinButtonHandle : textEntryHandle, SWT.DEFAULT,
                SWT.DEFAULT, false).y;
        int newHeight = dateEntryHeight;

        //Move button a little closer to entry field, by amount of padding.
        int newXpos = parentWidth - buttonSize.x - getGtkBorderPadding().left - getGtkBorderPadding().right;

        int newYPos = parentHeight / 2 - dateEntryHeight / 2;
        down.setBoundsInPixels(newXpos, newYPos, buttonSize.x, newHeight);
    }

    /**
     * Gets the border padding structure, which can be used to determine the inner padding of the text field.
     * Note, this function returns the correct padding only under GTK3.
     * Under Gtk2, it returns a constant.
     * @return GtkBorder object that holds the padding values.
     */
    GtkBorder getGtkBorderPadding() {
        //In Gtk3, acquire border.
        GtkBorder gtkBorderPadding = new GtkBorder();
        long contextHandle = GTK.GTK4 ? spinButtonHandle : textEntryHandle;
        long context = GTK.gtk_widget_get_style_context(contextHandle);
        int state_flag = GTK.GTK_VERSION < OS.VERSION(3, 18, 0) ? GTK.GTK_STATE_FLAG_NORMAL
                : GTK.gtk_widget_get_state_flags(contextHandle);
        gtk_style_context_get_padding(context, state_flag, gtkBorderPadding);
        return gtkBorderPadding;
    }

    boolean onNumberKeyInput(int key) {
        if (currentField == null) {
            return false;
        }
        int fieldName = getCalendarField(currentField);
        StringBuilder prefix = new StringBuilder();
        StringBuilder current = new StringBuilder();
        StringBuilder suffix = new StringBuilder();

        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        char c = iterator.first();
        do {
            if (isSameField(currentField, getFieldPosition(iterator))) {
                current.append(c);
            } else if (current.length() == 0) {
                prefix.append(c);
            } else {
                suffix.append(c);
            }
        } while ((c = iterator.next()) != CharacterIterator.DONE);

        if (typeBufferPos < 0) {
            typeBuffer.setLength(0);
            typeBuffer.append(current);
            typeBufferPos = 0;
        }
        if (key == GDK.GDK_BackSpace) {
            if (typeBufferPos > 0 && typeBufferPos <= typeBuffer.length()) {
                typeBuffer.deleteCharAt(typeBufferPos - 1);
                typeBufferPos--;
            }
        } else if (key == GDK.GDK_Delete) {
            if (typeBufferPos >= 0 && typeBufferPos < typeBuffer.length()) {
                typeBuffer.deleteCharAt(typeBufferPos);
            }
        } else {
            char newText = keyToString(key);
            if (!Character.isAlphabetic(newText) && !Character.isDigit(newText)) {
                return false;
            }
            if (fieldName == Calendar.AM_PM) {
                if (dateFormat instanceof SimpleDateFormat) {
                    String[] amPmStrings = ((SimpleDateFormat) dateFormat).getDateFormatSymbols().getAmPmStrings();
                    if (amPmStrings[Calendar.AM].charAt(0) == newText) {
                        setTextField(currentField, Calendar.AM);
                        return false;
                    } else if (amPmStrings[Calendar.PM].charAt(0) == newText) {
                        setTextField(currentField, Calendar.PM);
                        return false;
                    }
                }
            }
            if (typeBufferPos < typeBuffer.length()) {
                typeBuffer.replace(typeBufferPos, typeBufferPos + 1, Character.toString(newText));
            } else {
                typeBuffer.append(newText);
            }
            typeBufferPos++;
        }
        StringBuilder newText = new StringBuilder(prefix);
        newText.append(typeBuffer);
        newText.append(suffix);
        setText(newText.toString());
        setSelection(prefix.length() + typeBufferPos, prefix.length() + typeBuffer.length());
        currentField.setBeginIndex(prefix.length());
        currentField.setEndIndex(prefix.length() + typeBuffer.length());
        return false;
    }

    private char keyToString(int key) {
        // If numberpad keys were pressed.
        if (key >= GDK.GDK_KP_0 && key <= GDK.GDK_KP_9) {
            // convert numberpad button to regular key;
            key -= 65408;
        }
        return (char) key;
    }

    void updateControl() {
        if ((isDate() || isTime()) && textEntryHandle != 0) {
            setText(getFormattedString());
        }
        redraw();
    }

    @Override
    void register() {
        super.register();
        if (handle != 0 && display.getWidget(handle) == null)
            display.addWidget(handle, this);
        if (containerHandle != 0 && containerHandle != handle)
            display.addWidget(containerHandle, this);
        if (textEntryHandle != 0 && textEntryHandle != containerHandle)
            display.addWidget(textEntryHandle, this);
    }

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

    @Override
    void deregister() {
        super.deregister();
        if (handle != 0 && display.getWidget(handle) != null)
            display.removeWidget(handle);
        if (containerHandle != 0 && containerHandle != handle)
            display.removeWidget(containerHandle);
        if (textEntryHandle != 0 && textEntryHandle != containerHandle)
            display.removeWidget(textEntryHandle);
    }

    int getArrow(long widget) {
        updateControl();
        int adj_value = (int) GTK.gtk_adjustment_get_value(GTK.gtk_spin_button_get_adjustment(widget));
        int new_value = 0;
        if (isDate()) {
            FieldPosition firstField = getNextField(null);
            int firstFieldConstant = getCalendarField(firstField);
            new_value = calendar.get(getCalendarField(firstField));
            if (firstFieldConstant == Calendar.MONTH) {
                if ((style & SWT.SHORT) != 0) {
                    // adj_value returns the month as a number between 1-12
                    // new_value gets the month as a number between 0-11
                    // shift the adj_value by offset so that we get the correct arrow direction
                    adj_value--;
                } else if ((style & SWT.MEDIUM) != 0 || (style & SWT.LONG) != 0) {
                    // adj_value is either +1, 0, -1 when month is displayed as string
                    if (adj_value == 0) {
                        return 0;
                    } else {
                        return adj_value > 0 ? SWT.ARROW_UP : SWT.ARROW_DOWN;
                    }
                }
            }
        } else if (isTime()) {
            new_value = getHours();
            if (hasAmPm()) {
                // as getHours () has 24h format but spinner 12h format, new_value needs to be
                // converted to 12h format
                if (getHours() > 12) {
                    new_value = getHours() - 12;
                }
                if (new_value == 0)
                    new_value = 12;
            }
        }
        if (adj_value == 0 && firstTime)
            return 0;
        firstTime = false;
        if (adj_value == new_value)
            return 0;
        return adj_value > new_value ? SWT.ARROW_UP : SWT.ARROW_DOWN;
    }

    /**
     * Calculates appropriate width of GtkEntry and
     * adds Date/Time string to the Date/Time Spinner
     */
    void setText(String dateTimeText) {
        if (dateTimeText != null) {
            byte[] dateTimeConverted = Converter.wcsToMbcs(dateTimeText, true);
            //note, this is ignored if the control is in a fill-layout.
            GTK.gtk_entry_set_width_chars(textEntryHandle, dateTimeText.length());
            GTK.gtk_entry_set_text(textEntryHandle, dateTimeConverted);
            if (popupCalendar != null && calendar != null) {
                Date parse;
                try {
                    parse = dateFormat.parse(dateTimeText);
                } catch (ParseException e) {
                    //not a valid date (yet), return for now
                    return;
                }
                Calendar clone = (Calendar) calendar.clone();
                clone.setTime(parse);
                try {
                    popupCalendar.setDate(clone.get(Calendar.YEAR), clone.get(Calendar.MONTH),
                            clone.get(Calendar.DAY_OF_MONTH));
                } catch (SWTException e) {
                    if (e.code == SWT.ERROR_WIDGET_DISPOSED) {
                        //the calendar popup was disposed in the meantime so nothing to update
                        return;
                    }
                    throw e;
                }
            }
        }
    }

    @Override
    long gtk_key_press_event(long widget, long event) {
        if (!isReadOnly() && (isTime() || isDate())) {
            int[] key = new int[1];
            GDK.gdk_event_get_keyval(event, key);
            switch (key[0]) {
            case GDK.GDK_Up:
            case GDK.GDK_KP_Up:
                incrementField(+1);
                commitData();
                break;
            case GDK.GDK_Down:
            case GDK.GDK_KP_Down:
                incrementField(-1);
                commitData();
                break;
            case GDK.GDK_Tab:
            case GDK.GDK_Right:
            case GDK.GDK_KP_Right:
                selectField(getNextField(currentField));
                sendEvent(SWT.Traverse);
                break;
            case GDK.GDK_Left:
            case GDK.GDK_KP_Left:
                selectField(getPreviousField(currentField));
                sendEvent(SWT.Traverse);
                break;
            case GDK.GDK_Home:
            case GDK.GDK_KP_Home:
                /* Set the value of the current field to its minimum */
                if (currentField != null) {
                    setTextField(currentField, calendar.getActualMinimum(getCalendarField(currentField)));
                }
                break;
            case GDK.GDK_End:
            case GDK.GDK_KP_End:
                /* Set the value of the current field to its maximum */
                if (currentField != null) {
                    setTextField(currentField, calendar.getActualMaximum(getCalendarField(currentField)));
                }
                break;
            default:
                onNumberKeyInput(key[0]);
            }
        }
        return 1;
    }

    void commitData() {
        try {
            Date date = dateFormat.parse(getText());
            calendar.setTime(date);
        } catch (ParseException e) {
            // invalid value, input will reset...
        }
        updateControl();
    }

    /** returns selected text **/
    Point getSelection() {
        checkWidget();
        Point selection;
        int[] start = new int[1];
        int[] end = new int[1];
        GTK.gtk_editable_get_selection_bounds(textEntryHandle, start, end);
        long ptr = GTK.gtk_entry_get_text(textEntryHandle);
        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]);
        return selection;

    }

    /**
     * Returns a string containing a copy of the contents of the
     * receiver's text field, or an empty string if there are no
     * contents.
     *
     * @return Spinner'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>
     */
    String getText() {
        checkWidget();
        if (textEntryHandle != 0) {
            long str = GTK.gtk_entry_get_text(textEntryHandle);
            if (str == 0)
                return "";
            int length = C.strlen(str);
            byte[] buffer = new byte[length];
            C.memmove(buffer, str, length);
            return new String(Converter.mbcsToWcs(buffer));
        }
        return "";
    }

    /**
     * returns GtkEntry starting from index and ending with index
     * provided by the user
     */
    String getText(String str, int start, int end) {
        checkWidget();
        if (!(start <= end && 0 <= end))
            return "";
        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);
    }

    void setSelection(int start, int end) {
        checkWidget();
        long ptr = GTK.gtk_entry_get_text(textEntryHandle);
        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(textEntryHandle, start);
        GTK.gtk_editable_select_region(textEntryHandle, start, end);
    }

    void setTextField(FieldPosition field, int value) {
        int validValue = validateValueBounds(field, value);
        setFieldOfInternalDataStructure(field, validValue);
        setFieldOfInternalDataStructure(field, value);
        updateControl();
        if (currentField != null) {
            selectField(currentField);
        }
    }

    private int validateValueBounds(FieldPosition field, int value) {
        int calendarField = getCalendarField(field);
        int max = calendar.getActualMaximum(calendarField);
        int min = calendar.getActualMinimum(calendarField);
        if (calendarField == Calendar.YEAR) {
            max = MAX_YEAR;
            min = MIN_YEAR;
            /* Special case: convert 1 or 2-digit years into reasonable 4-digit years. */
            int currentYear = Calendar.getInstance().get(Calendar.YEAR);
            int currentCentury = (currentYear / 100) * 100;
            if (value < (currentYear + 30) % 100)
                value += currentCentury;
            else if (value < 100)
                value += currentCentury - 100;
        }
        if (value > max)
            value = min; // wrap
        if (value < min)
            value = max; // wrap
        return value;
    }

    @Override
    long gtk_button_release_event(long widget, long event) {
        if (isDate() || isTime()) {
            int[] eventButton = new int[1];
            GDK.gdk_event_get_button(event, eventButton);
            if (eventButton[0] == 1) { // left mouse button.
                onTextMouseClick();
            }
        }
        return super.gtk_button_release_event(widget, event);
    }

    /**
     * Output signal is called when Spinner's arrow buttons are triggered,
     * usually by clicking the mouse on the [gtk2: up/down] [gtk3: +/-] buttons.
     * On every click output is called twice presenting current and previous value.
     * This method compares two values and determines if Up or down arrow was called.
     */
    @Override
    long gtk_output(long widget) {
        if (calendar == null) {
            return 0; //Guard: Object not fully initialized yet.
        }
        int arrowType = getArrow(widget);
        switch (arrowType) {
        case SWT.ARROW_UP: // Gtk3 "+" button.
            commitData();
            incrementField(+1);
            break;
        case SWT.ARROW_DOWN: // Gtk3 "-" button.
            commitData();
            incrementField(-1);
            break;
        }
        return 1;
    }

    void replaceCurrentlySelectedTextRegion(String string) {
        checkWidget();
        if (string == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        byte[] buffer = Converter.wcsToMbcs(string, false);
        int[] start = new int[1], end = new int[1];
        GTK.gtk_editable_get_selection_bounds(textEntryHandle, start, end);
        GTK.gtk_editable_delete_selection(textEntryHandle);
        GTK.gtk_editable_insert_text(textEntryHandle, buffer, buffer.length, start);
        GTK.gtk_editable_set_position(textEntryHandle, start[0]);
    }

    void onTextMouseClick() {
        if (calendar == null) {
            return; // Guard: Object not fully initialized yet.
        }
        int clickPosition = getSelection().x;
        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        iterator.first();
        int pos = 0;
        do {
            FieldPosition position = getFieldPosition(iterator);
            iterator.setIndex(iterator.getRunLimit());
            if (isSameField(position, currentField)) {
                // use the current field instead then!
                position = currentField;
            }
            int fieldWidth = position.getEndIndex() - position.getBeginIndex();
            pos += fieldWidth;
            if (position.getFieldAttribute() == null) {
                continue;
            }
            if (pos >= clickPosition) {
                FieldPosition selectField = new FieldPosition(position.getFieldAttribute());
                selectField.setBeginIndex(pos - fieldWidth);
                selectField.setEndIndex(pos);
                selectField(selectField);
                break;
            }
        } while (iterator.current() != CharacterIterator.DONE);

    }

    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);
    }

    void selectAll() {
        checkWidget();
        if (textEntryHandle != 0)
            GTK.gtk_editable_select_region(textEntryHandle, 0, -1);
    }

    void hideDateTime() {
        if (isDate() || isTime()) {
            GTK.gtk_widget_hide(fixedHandle);
        }
    }

    @Override
    void releaseWidget() {
        super.releaseWidget();
        if (fixedHandle != 0)
            hideDateTime();
    }

    /**
     * Returns a field with updated positionla data
     *
     * @param field
     * @return
     */
    private FieldPosition updateField(FieldPosition field) {
        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        while (iterator.current() != CharacterIterator.DONE) {
            FieldPosition current = getFieldPosition(iterator);
            iterator.setIndex(iterator.getRunLimit());
            if (field == null || isSameField(current, field)) {
                return current;
            }
        }
        return field;
    }

    /**
     * Given a {@link FieldPosition} searches the next field in the format string
     *
     * @param field
     *            the Field to start from
     * @return the next {@link FieldPosition}
     */
    private FieldPosition getNextField(FieldPosition field) {
        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        FieldPosition first = null;
        boolean found = false;
        while (iterator.current() != CharacterIterator.DONE) {
            FieldPosition current = getFieldPosition(iterator);
            iterator.setIndex(iterator.getRunLimit());
            if (current.getFieldAttribute() == null) {
                continue;
            }
            if (found) {
                return current;
            }
            if (first == null) {
                first = current;
            }
            if (isSameField(current, field)) {
                found = true;
            }
        }
        return first;
    }

    /**
     *
     * @param field
     * @return the next field of the given one
     */
    private FieldPosition getPreviousField(FieldPosition field) {
        AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
        FieldPosition last = null;
        do {
            FieldPosition current = getFieldPosition(iterator);
            if (isSameField(current, field)) {
                if (last != null) {
                    return last;
                }
            }
            if (current.getFieldAttribute() != null) {
                last = current;
            }
            iterator.setIndex(iterator.getRunLimit());
        } while (iterator.current() != CharacterIterator.DONE);
        return last;
    }

    /**
     * Searches the current postion of the iterator for a {@link Field} and
     * constructs a {@link FieldPosition} from it
     *
     * @param iterator
     *            the iterator to use
     * @return a new {@link FieldPosition}
     */
    private static FieldPosition getFieldPosition(AttributedCharacterIterator iterator) {
        Set<Attribute> keySet = iterator.getAttributes().keySet();
        for (Attribute attribute : keySet) {
            if (attribute instanceof Field) {
                return getFieldPosition((Field) attribute, iterator);
            }
        }
        return getFieldPosition((Field) null, iterator);
    }

    /**
     * creates a {@link FieldPosition} out of a {@link Field} and and a
     * {@link AttributedCharacterIterator}s current position
     *
     * @param field
     *            the field to use
     * @param iterator
     *            the iterator to extract the data from
     * @return a {@link FieldPosition} init to this Field and begin/end index
     */
    private static FieldPosition getFieldPosition(Field field, AttributedCharacterIterator iterator) {
        FieldPosition position = new FieldPosition(field);
        position.setBeginIndex(iterator.getRunStart());
        position.setEndIndex(iterator.getRunLimit());
        return position;
    }

    /**
     * Check if the given {@link FieldPosition} are considdered "the same", this is
     * when both are not <code>null</code> and reference the same
     * {@link java.text.Format.Field} attribute, or both of them have no
     * fieldattribute and have the same position
     *
     * @param p1
     *            first position to compare
     * @param p2
     *            second position to compare
     * @return <code>true</code> if considered the same, <code>false</code>
     *         otherwise
     */
    private static boolean isSameField(FieldPosition p1, FieldPosition p2) {
        if (p1 == p2) {
            return true;
        }
        if (p1 == null || p2 == null) {
            return false;
        }
        if (p1.getFieldAttribute() == null && p2.getFieldAttribute() == null) {
            return p1.equals(p2);
        }
        if (p1.getFieldAttribute() == null) {
            return false;
        }
        return p1.getFieldAttribute().equals(p2.getFieldAttribute());
    }

    /**
     * Extracts the calendarfield for the given fieldposition
     *
     * @param fieldPosition
     * @return the {@link Calendar} field or -1 if this is not a valid Fieldposition
     */
    private static int getCalendarField(FieldPosition fieldPosition) {
        if ((fieldPosition.getFieldAttribute() instanceof Field)) {
            return getCalendarField((Field) fieldPosition.getFieldAttribute());
        } else {
            return -1;
        }
    }

    /**
     * Extracts the calendarfield transforming HOUR1 types to HOUR0
     *
     * @param field
     * @return the calendarfield coresponding to the {@link Field}
     */
    private static int getCalendarField(Field field) {
        if (Field.HOUR1.equals(field)) {
            field = Field.HOUR0;
        } else if (Field.HOUR_OF_DAY1.equals(field)) {
            field = Field.HOUR_OF_DAY0;
        }
        return field.getCalendarField();
    }

}