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