1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Red Hat         - GtkSpinButton rewrite. 2014.10.09
14  *     Lablicate GmbH  - add locale support/improve editing support for date/time styles. 2017.02.08
15  *******************************************************************************/
16 package org.eclipse.swt.widgets;
17 
18 import java.text.*;
19 import java.text.AttributedCharacterIterator.*;
20 import java.text.DateFormat.*;
21 import java.util.*;
22 
23 import org.eclipse.swt.*;
24 import org.eclipse.swt.accessibility.*;
25 import org.eclipse.swt.events.*;
26 import org.eclipse.swt.graphics.*;
27 import org.eclipse.swt.internal.*;
28 import org.eclipse.swt.internal.gtk.*;
29 import org.eclipse.swt.internal.gtk3.*;
30 import org.eclipse.swt.internal.gtk4.*;
31 
32 /*
33  * Developer note: Unit tests for this class can be found under:
34  * org.eclipse.swt.tests.junit.Test_org_eclipse_swt_widgets_DateTime
35  */
36 
37 /**
38  * Instances of this class are selectable user interface
39  * objects that allow the user to enter and modify date
40  * or time values.
41  * <p>
42  * Note that although this class is a subclass of <code>Composite</code>,
43  * it does not make sense to add children to it, or set a layout on it.
44  * </p>
45  * <dl>
46  * <dt><b>Styles:</b></dt>
47  * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG, DROP_DOWN, CALENDAR_WEEKNUMBERS</dd>
48  * <dt><b>Events:</b></dt>
49  * <dd>DefaultSelection, Selection</dd>
50  * </dl>
51  * <p>
52  * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified,
53  * and only one of the styles SHORT, MEDIUM, or LONG may be specified.
54  * The DROP_DOWN style is only valid with the DATE style.
55  * </p><p>
56  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
57  * </p>
58  *
59  * @see <a href="http://www.eclipse.org/swt/snippets/#datetime">DateTime snippets</a>
60  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
61  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
62  *
63  * @since 3.3
64  * @noextend This class is not intended to be subclassed by clients.
65  */
66 public class DateTime extends Composite {
67 	int day, month, year, hours, minutes, seconds;
68 
69 	/**
70 	 * Major handles of this class.
71 	 * Note, these can vary or all equal each other depending on Date/Time/Calendar/Drop_down
72 	 * configuration used. See createHandle () */
73 	long textEntryHandle, spinButtonHandle,
74 	containerHandle,
75 	calendarHandle;
76 
77 	/* Emulated DATE and TIME fields */
78 	Calendar calendar;
79 	Button down;
80 	FieldPosition currentField;
81 	StringBuilder typeBuffer = new StringBuilder();
82 	int typeBufferPos = -1;
83 	boolean firstTime = true;
84 	private DateFormat dateFormat;
85 	/* DROP_DOWN calendar fields for DATE */
86 	Color fg, bg;
87 	boolean hasFocus;
88 	int savedYear, savedMonth, savedDay;
89 	Shell popupShell;
90 	DateTime popupCalendar;
91 	Listener popupListener, popupFilter;
92 
93 	Point prefferedSize;
94 	Locale locale;
95 
96 	/** Used when SWT.DROP_DOWN is set */
97 	Listener mouseEventListener;
98 
99 	/*
100 	 * Used for easier access to format pattern of DATE and TIME.
101 	 * See https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
102 	 */
103 	static final String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yy";
104 	static final String DEFAULT_MEDIUM_DATE_FORMAT = "d-MMM-yyyy";
105 	static final String DEFAULT_LONG_DATE_FORMAT = "MMMM d, yyyy";
106 	static final String DEFAULT_SHORT_TIME_FORMAT = "h:mm a";
107 	static final String DEFAULT_MEDIUM_TIME_FORMAT = "h:mm:ss a";
108 	static final String DEFAULT_LONG_TIME_FORMAT = "h:mm:ss z a";
109 	static final int MIN_YEAR = 1752; // Gregorian switchover in North America: September 19, 1752
110 	static final int MAX_YEAR = 9999;
111 	static final int SPACE_FOR_CURSOR = 1;
112 
113 	private int mdYear;
114 
115 	private int mdMonth;
116 
117 /**
118  * Constructs a new instance of this class given its parent
119  * and a style value describing its behavior and appearance.
120  * <p>
121  * The style value is either one of the style constants defined in
122  * class <code>SWT</code> which is applicable to instances of this
123  * class, or must be built by <em>bitwise OR</em>'ing together
124  * (that is, using the <code>int</code> "|" operator) two or more
125  * of those <code>SWT</code> style constants. The class description
126  * lists the style constants that are applicable to the class.
127  * Style bits are also inherited from superclasses.
128  * </p>
129  *
130  * @param parent a composite control which will be the parent of the new instance (cannot be null)
131  * @param style the style of control to construct
132  *
133  * @exception IllegalArgumentException <ul>
134  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
135  * </ul>
136  * @exception SWTException <ul>
137  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
138  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
139  * </ul>
140  *
141  * @see SWT#DATE
142  * @see SWT#TIME
143  * @see SWT#CALENDAR
144  * @see SWT#CALENDAR_WEEKNUMBERS
145  * @see SWT#SHORT
146  * @see SWT#MEDIUM
147  * @see SWT#LONG
148  * @see SWT#DROP_DOWN
149  * @see Widget#checkSubclass
150  * @see Widget#getStyle
151  */
DateTime(Composite parent, int style)152 public DateTime (Composite parent, int style) {
153 	super (parent, checkStyle (style));
154 	if (isDate () || isTime ()) {
155 		createText ();
156 	}
157 
158 	if (isCalendar ()) {
159 		GTK.gtk_calendar_mark_day (calendarHandle, Calendar.getInstance ().get (Calendar.DAY_OF_MONTH));
160 	}
161 
162 	if (isDateWithDropDownButton ()) {
163 		createDropDownButton ();
164 		createPopupShell (-1, -1, -1);
165 		addListener (SWT.Resize, event -> setDropDownButtonSize ());
166 	}
167 	initAccessible ();
168 
169 	if (isDateWithDropDownButton ()) {
170 		//Date w/ drop down button is in containers.
171 		//first time round we set the bounds manually for correct Right_to_left behaviour
172 		Point size = computeSizeInPixels (SWT.DEFAULT, SWT.DEFAULT);
173 		setBoundsInPixels (0, 0, size.x, size.y);
174 	}
175 }
176 
createText()177 void createText() {
178 	String property = System.getProperty("swt.datetime.locale");
179 	if (property == null || property.isEmpty()) {
180 		locale = Locale.getDefault();
181 	} else {
182 		locale = Locale.forLanguageTag(property);
183 	}
184 	dateFormat = getFormat(locale, style);
185 	dateFormat.setLenient(false);
186 	calendar = Calendar.getInstance(locale);
187 	updateControl();
188 	selectField(updateField(currentField));
189 }
190 
getFormat(Locale locale, int style)191 DateFormat getFormat(Locale locale, int style) {
192 	int dfStyle;
193 	if ((style & SWT.LONG) != 0) {
194 		dfStyle = DateFormat.LONG;
195 	} else if ((style & SWT.SHORT) != 0) {
196 		dfStyle = DateFormat.SHORT;
197 	} else {
198 		dfStyle = DateFormat.MEDIUM;
199 	}
200 	if (isDate()) {
201 		return DateFormat.getDateInstance(dfStyle, locale);
202 	} else if (isTime()) {
203 		return DateFormat.getTimeInstance(dfStyle, locale);
204 	} else {
205 		throw new IllegalStateException("can only be called for date or time widgets!");
206 	}
207 }
208 
checkStyle(int style)209 static int checkStyle (int style) {
210 	/*
211 	* Even though it is legal to create this widget
212 	* with scroll bars, they serve no useful purpose
213 	* because they do not automatically scroll the
214 	* widget's client area.  The fix is to clear
215 	* the SWT style.
216 	*/
217 	style &= ~(SWT.H_SCROLL | SWT.V_SCROLL);
218 
219 	style = checkBits (style, SWT.DATE, SWT.TIME, SWT.CALENDAR, 0, 0, 0);
220 	if ((style & SWT.DATE) == 0) style &=~ SWT.DROP_DOWN;
221 	return checkBits (style, SWT.MEDIUM, SWT.SHORT, SWT.LONG, 0, 0, 0);
222 }
223 
224 /**
225  * Adds the listener to the collection of listeners who will
226  * be notified when the control is selected by the user, by sending
227  * it one of the messages defined in the <code>SelectionListener</code>
228  * interface.
229  * <p>
230  * <code>widgetSelected</code> is called when the user changes the control's value.
231  * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed.
232  * </p>
233  *
234  * @param listener the listener which should be notified
235  *
236  * @exception IllegalArgumentException <ul>
237  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
238  * </ul>
239  * @exception SWTException <ul>
240  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
241  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
242  * </ul>
243  *
244  * @see SelectionListener
245  * @see #removeSelectionListener
246  * @see SelectionEvent
247  */
addSelectionListener(SelectionListener listener)248 public void addSelectionListener (SelectionListener listener) {
249 	checkWidget ();
250 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
251 	TypedListener typedListener = new TypedListener (listener);
252 	addListener (SWT.Selection, typedListener);
253 	addListener (SWT.DefaultSelection, typedListener);
254 }
255 
256 @Override
checkSubclass()257 protected void checkSubclass () {
258 	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
259 }
260 
261 /**
262  * Compute the native text entry size when the formatted text inside the entry
263  * is at the longest length possible. i.e. Assume DATE/HOUR field to be double digit,
264  * MONTH field for SWT.DATE | SWT.LONG is the longest text.
265  *
266  * @param wHint
267  * @param hHint
268  * @param changed
269  * @return text entry size to hold the longest possible formatted text.
270  */
computeMaxTextSize(int wHint, int hHint, boolean changed)271 Point computeMaxTextSize (int wHint, int hHint, boolean changed) {
272 	String currentText = getFormattedString();
273 	String formatPattern = getComputeSizeString(style);
274 
275 	switch (formatPattern) {
276 		case DEFAULT_MEDIUM_DATE_FORMAT:
277 			// Make the DATE field a double digit
278 			String longDateText = currentText.replaceFirst("\\d{1,2}", "00");
279 			setText(longDateText);
280 			break;
281 		case DEFAULT_LONG_DATE_FORMAT:
282 			// Make the MONTH field the longest length possible, the DATE field a double digit.
283 			Set<String> months = calendar.getDisplayNames(Calendar.MONTH, Calendar.LONG, locale).keySet();
284 			String longestMonth = Collections.max(months, (s1, s2) -> s1.length() - s2.length()); // Probably September
285 			String doubleDigitDate = currentText.replaceFirst("\\d{1,2}", "00");
286 			String longText = doubleDigitDate.replaceFirst("[^\\s]+", longestMonth);
287 			setText(longText);
288 			break;
289 		case DEFAULT_SHORT_TIME_FORMAT:
290 		case DEFAULT_MEDIUM_TIME_FORMAT:
291 		case DEFAULT_LONG_TIME_FORMAT:
292 			// Make the HOUR field a double digit
293 			String longTimeText = currentText.replaceFirst("\\d{1,2}", "00");
294 			setText(longTimeText);
295 			break;
296 		default:
297 			// Fixed length for DEFAULT_SHORT_DATE_FORMAT, no need to adjust text length.
298 	}
299 
300 	Point textSize = computeNativeSize (GTK.GTK4 ? handle : textEntryHandle, wHint, hHint, changed);
301 	// Change the text back to match the current calendar
302 	updateControl();
303 	return textSize;
304 }
305 
306 @Override
computeSizeInPixels(int wHint, int hHint, boolean changed)307 Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
308 	checkWidget ();
309 
310 	int width = 0, height = 0;
311 	//For Date and Time, we cache the preffered size as there is no need to recompute it.
312 	if (!changed && (isDate () || isTime ()) && prefferedSize != null) {
313 		width = (wHint != SWT.DEFAULT) ? wHint : prefferedSize.x;
314 		height= (hHint != SWT.DEFAULT) ? hHint : prefferedSize.y;
315 		return new Point (width,height);
316 	}
317 
318 	if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
319 		if (isCalendar ()) {
320 			Point size = computeNativeSize (containerHandle, wHint, hHint, changed);
321 			width = size.x;
322 			height = size.y;
323 		} else {
324 			/*
325 			 * Bug 538612: Computing the native size for textEntry when the current text
326 			 * is not the longest length possible causes sizing issues when the entry text
327 			 * is changed. Fix is to always allocate enough size to hold the longest possible
328 			 * formatted text.
329 			 */
330 			Point textSize = computeMaxTextSize (wHint, hHint, changed);
331 			Rectangle trim = computeTrimInPixels (0,0, textSize.x,textSize.y);
332 			if (isDateWithDropDownButton ()){
333 				Point buttonSize = down.computeSizeInPixels (SWT.DEFAULT, SWT.DEFAULT, changed);
334 				width = trim.width + buttonSize.x;
335 				height = Math.max (trim.height, buttonSize.y);
336 			} else if (isDate () || isTime ()) {
337 				width = trim.width;
338 				height = trim.height;
339 			}
340 		}
341 	}
342 	if (width == 0) width = DEFAULT_WIDTH;
343 	if (height == 0) height = DEFAULT_HEIGHT;
344 	if (wHint != SWT.DEFAULT) width = wHint;
345 	if (hHint != SWT.DEFAULT) height = hHint;
346 	int borderWidth = getBorderWidthInPixels ();
347 
348 	if (prefferedSize == null && isDateWithDropDownButton ()) {
349 		prefferedSize = new Point (width + 2*borderWidth, height+ 2*borderWidth);
350 		return prefferedSize;
351 	} else {
352 		return new Point (width + 2*borderWidth, height+ 2*borderWidth);
353 	}
354 }
355 
356 @Override
computeTrimInPixels(int x, int y, int width, int height)357 Rectangle computeTrimInPixels (int x, int y, int width, int height) {
358 	if (isCalendar ()) {
359 		return super.computeTrimInPixels (x, y, width, height);
360 	}
361 
362 	checkWidget ();
363 	Rectangle trim = super.computeTrimInPixels (x, y, width, height);
364 	int xborder = 0, yborder = 0;
365 		GtkBorder tmp = new GtkBorder ();
366 		long context = GTK.gtk_widget_get_style_context (textEntryHandle);
367 		int state_flag = GTK.gtk_widget_get_state_flags(textEntryHandle);
368 		gtk_style_context_get_padding(context, state_flag, tmp);
369 		trim.x -= tmp.left;
370 		trim.y -= tmp.top;
371 		trim.width += tmp.left + tmp.right;
372 		trim.height += tmp.top + tmp.bottom;
373 		if ((style & SWT.BORDER) != 0) {
374 			int state = GTK.gtk_widget_get_state_flags(textEntryHandle);
375 			gtk_style_context_get_border(context, state, tmp);
376 			trim.x -= tmp.left;
377 			trim.y -= tmp.top;
378 			trim.width += tmp.left + tmp.right;
379 			trim.height += tmp.top + tmp.bottom;
380 		}
381 		trim.x -= xborder;
382 		trim.y -= yborder;
383 		trim.width += 2 * xborder;
384 		trim.height += 2 * yborder;
385 		trim.width += SPACE_FOR_CURSOR;
386 		return new Rectangle (trim.x, trim.y, trim.width, trim.height);
387 }
388 
389 
390 @Override
createHandle(int index)391 void createHandle (int index) {
392 	createHandle ();
393 }
394 
395 /**
396  * Here we carefully define the three internal handles:
397  *  textEntryHandle
398  *  containerHandle
399  *  calendarHandle
400  */
createHandle()401 void createHandle () {
402 	if (isCalendar ()) {
403 		state |= HANDLE;
404 		createHandleForFixed ();
405 		createHandleForCalendar ();
406 
407 	} else {
408 		createHandleForFixed ();
409 		if (isDateWithDropDownButton ()) {
410 			createHandleForDateWithDropDown ();
411 		} else {
412 			createHandleForDateTime ();
413 		}
414 		GTK.gtk_editable_set_editable (textEntryHandle, (style & SWT.READ_ONLY) == 0);
415 	}
416 }
417 
createHandleForFixed()418 private void createHandleForFixed () {
419 	fixedHandle = OS.g_object_new (display.gtk_fixed_get_type (), 0);
420 	if (fixedHandle == 0) error (SWT.ERROR_NO_HANDLES);
421 	if (!GTK.GTK4) GTK3.gtk_widget_set_has_window(fixedHandle, true);
422 }
423 
createHandleForCalendar()424 private void createHandleForCalendar () {
425 	calendarHandle = GTK.gtk_calendar_new ();
426 	if (calendarHandle == 0) error (SWT.ERROR_NO_HANDLES);
427 
428 	//Calendar becomes container in this case.
429 	handle = calendarHandle;
430 	containerHandle = calendarHandle;
431 
432 	if (GTK.GTK4) {
433 		OS.swt_fixed_add(fixedHandle, calendarHandle);
434 
435 		GTK4.gtk_calendar_set_show_heading(calendarHandle, true);
436 		GTK4.gtk_calendar_set_show_day_names(calendarHandle, true);
437 		GTK4.gtk_calendar_set_show_week_numbers(calendarHandle, showWeekNumbers());
438 	} else {
439 		GTK3.gtk_container_add (fixedHandle, calendarHandle);
440 
441 		int flags = GTK.GTK_CALENDAR_SHOW_HEADING | GTK.GTK_CALENDAR_SHOW_DAY_NAMES;
442 		if (showWeekNumbers()) {
443 			flags |= GTK.GTK_CALENDAR_SHOW_WEEK_NUMBERS;
444 		}
445 		GTK3.gtk_calendar_set_display_options (calendarHandle, flags);
446 		GTK.gtk_widget_show (calendarHandle);
447 	}
448 }
449 
createHandleForDateWithDropDown()450 private void createHandleForDateWithDropDown () {
451 	handle = gtk_box_new(GTK.GTK_ORIENTATION_HORIZONTAL, false, 0);
452 	if (handle == 0) error(SWT.ERROR_NO_HANDLES);
453 	containerHandle = handle;
454 
455 	textEntryHandle = GTK.gtk_entry_new();
456 	if (textEntryHandle == 0) error(SWT.ERROR_NO_HANDLES);
457 
458 	if (GTK.GTK4) {
459 		OS.swt_fixed_add(fixedHandle, containerHandle);
460 		GTK4.gtk_box_append(containerHandle, textEntryHandle);
461 	} else {
462 		GTK3.gtk_container_add(fixedHandle, containerHandle);
463 		GTK3.gtk_container_add(containerHandle, textEntryHandle);
464 		GTK.gtk_widget_show(containerHandle);
465 		GTK.gtk_widget_show(textEntryHandle);
466 	}
467 
468 	// In GTK 3 font description is inherited from parent widget which is not how SWT has always worked,
469 	// reset to default font to get the usual behavior
470 	setFontDescription(defaultFont().handle);
471 }
472 
createHandleForDateTime()473 private void createHandleForDateTime () {
474 	long adjusment = GTK.gtk_adjustment_new (0, -9999, 9999, 1, 0, 0);
475 	if (GTK.GTK4) {
476 		handle =  GTK.gtk_spin_button_new(adjusment, 1, 0);
477 		textEntryHandle = GTK4.gtk_widget_get_first_child(handle);
478 		containerHandle = spinButtonHandle;
479 	} else {
480 		textEntryHandle = GTK.gtk_spin_button_new (adjusment, 1, 0);
481 		handle = textEntryHandle;
482 		containerHandle = textEntryHandle;
483 	}
484 	if (textEntryHandle == 0) error (SWT.ERROR_NO_HANDLES);
485 
486 	if (GTK.GTK4) {
487 		OS.swt_fixed_add(fixedHandle, handle);
488 	} else {
489 		GTK3.gtk_container_add (fixedHandle, handle);
490 	}
491 
492 	GTK.gtk_spin_button_set_numeric (handle, false);
493 	GTK.gtk_spin_button_set_wrap (handle, (style & SWT.WRAP) != 0);
494 }
495 
createDropDownButton()496 void createDropDownButton () {
497 	down = new Button (this, SWT.ARROW  | SWT.DOWN);
498 	GTK.gtk_widget_set_can_focus (down.handle, false);
499 	down.addListener (SWT.Selection, event -> {
500 		setFocus ();
501 		dropDownCalendar (!isDropped ());
502 	});
503 
504 	popupListener = event -> {
505 		if (event.widget == popupShell) {
506 			popupShellEvent (event);
507 			return;
508 		}
509 		if (event.widget == popupCalendar) {
510 			popupCalendarEvent (event);
511 			return;
512 		}
513 		if (event.widget == DateTime.this) {
514 			onDispose (event);
515 			return;
516 		}
517 		if (event.widget == getShell ()) {
518 			getDisplay ().asyncExec (() -> {
519 				if (isDisposed ()) return;
520 				handleFocus (SWT.FocusOut);
521 			});
522 		}
523 	};
524 	popupFilter = event -> {
525 		Shell shell = ((Control)event.widget).getShell ();
526 		if (shell == DateTime.this.getShell ()) {
527 			handleFocus (SWT.FocusOut);
528 		}
529 	};
530 }
531 
createPopupShell(int year, int month, int day)532 void createPopupShell (int year, int month, int day) {
533 	popupShell = new Shell (getShell (), SWT.NO_TRIM | SWT.ON_TOP);
534 	int popupStyle = SWT.CALENDAR;
535 	if (showWeekNumbers()) {
536 		popupStyle |= SWT.CALENDAR_WEEKNUMBERS;
537 	}
538 	popupCalendar = new DateTime (popupShell, popupStyle);
539 	if (font != null) popupCalendar.setFont (font);
540 	if (fg != null) popupCalendar.setForeground (fg);
541 	if (bg != null) popupCalendar.setBackground (bg);
542 
543 	mouseEventListener = event -> {
544 		if (event.widget instanceof Control) {
545 			Control c = (Control)event.widget;
546 			if (c != down && c.getShell () != popupShell)
547 				dropDownCalendar (false);
548 		}
549 	};
550 
551 	int [] listeners = {SWT.Close, SWT.MouseUp};
552 	for (int i=0; i < listeners.length; i++) {
553 		popupShell.addListener (listeners [i], popupListener);
554 	}
555 	listeners = new int [] {SWT.MouseDown, SWT.MouseUp, SWT.Selection, SWT.Traverse, SWT.KeyDown, SWT.KeyUp, SWT.FocusIn, SWT.FocusOut, SWT.Dispose};
556 	for (int i=0; i < listeners.length; i++) {
557 		popupCalendar.addListener (listeners [i], popupListener);
558 	}
559 	addListener (SWT.Dispose, popupListener);
560 	if (year != -1) popupCalendar.setDate (year, month, day);
561 }
562 
563 @Override
setFontDescription(long font)564 void setFontDescription (long font) {
565 	if (isDateWithDropDownButton ()) {
566 		prefferedSize = null; //flush cache for computeSize as font can cause size to change.
567 		setFontDescription (textEntryHandle, font);
568 	}
569 	super.setFontDescription (font);
570 }
571 
572 @Override
checkSubwindow()573 boolean checkSubwindow () {
574 	return false;
575 }
576 
577 @Override
createWidget(int index)578 void createWidget (int index) {
579 	super.createWidget (index);
580 	if (isCalendar ()) {
581 		getDate ();
582 	}
583 }
584 
onDispose(Event event)585 void onDispose (Event event) {
586 	if (popupShell != null && !popupShell.isDisposed ()) {
587 		popupCalendar.removeListener (SWT.Dispose, popupListener);
588 		popupShell.dispose ();
589 	}
590 	Shell shell = getShell ();
591 	shell.removeListener (SWT.Deactivate, popupListener);
592 	Display display = getDisplay ();
593 	display.removeFilter (SWT.FocusIn, popupFilter);
594 	popupShell = null;
595 	popupCalendar = null;
596 	down = null;
597 }
598 
599 /**
600  * Called when pressing the SWT.DROP_DOWN button on a Date Field
601  * @param drop true if the calendar is suppose to drop down.
602  */
dropDownCalendar(boolean drop)603 void dropDownCalendar (boolean drop) {
604 	if (drop == isDropped ()) return;
605 
606 	if (!drop) {
607 		hideDropDownCalendar ();
608 		return;
609 	}
610 
611 	setCurrentDate ();
612 
613 	if (getShell () != popupShell.getParent ()) {
614 		recreateCalendar ();
615 	}
616 
617 	//This is the x/y/width/height of the container of DateTime
618 	Point containerBounds = getSizeInPixels ();
619 	Point calendarSize = popupCalendar.computeSizeInPixels (SWT.DEFAULT, SWT.DEFAULT, false);
620 
621 	//Set the inner calendar pos/size. (not the popup shell pos/size)
622 	popupCalendar.setBoundsInPixels (1, 1, Math.max (containerBounds.x - 2, calendarSize.x), calendarSize.y);
623 
624 	//Set Date & focus current day
625 	popupCalendar.setDate (savedYear, savedMonth, savedDay);
626 	focusDayOnPopupCalendar ();
627 
628 	Display display = getDisplay ();
629 
630 	//To display popup calendar, we need to know where the parent is relative to the whole screen.
631 	Rectangle coordsRelativeToScreen = display.mapInPixels (getParent (), null, getBoundsInPixels ());
632 	Rectangle displayRect = DPIUtil.autoScaleUp(getMonitor ().getClientArea ());
633 
634 	showPopupShell (containerBounds, calendarSize, coordsRelativeToScreen, displayRect);
635 
636 	display.addFilter (SWT.MouseDown, mouseEventListener);
637 }
638 
showPopupShell(Point containerBounds, Point calendarSize, Rectangle coordsRelativeToScreen, Rectangle displayRect)639 private void showPopupShell (Point containerBounds, Point calendarSize, Rectangle coordsRelativeToScreen,
640 		Rectangle displayRect) {
641 	int width = Math.max (containerBounds.x, calendarSize.x + 2);
642 	int height = calendarSize.y + 2;
643 	int y = calculateCalendarYpos (containerBounds, coordsRelativeToScreen, height, displayRect);
644 	int x = calculateCalendarXpos (calendarSize, coordsRelativeToScreen, displayRect, width);
645 
646 	popupShell.setBoundsInPixels (x, y, width, height);
647 	popupShell.setVisible (true);
648 	if (isFocusControl ()) {
649 		popupCalendar.setFocus ();
650 	}
651 }
652 
calculateCalendarYpos(Point containerBounds, Rectangle coordsRelativeToScreen, int height, Rectangle displayRect)653 private int calculateCalendarYpos (Point containerBounds, Rectangle coordsRelativeToScreen, int height,
654 		Rectangle displayRect) {
655 	int dateEntryHeight = computeNativeSize (containerHandle, SWT.DEFAULT, SWT.DEFAULT, false).y;
656 	int y = coordsRelativeToScreen.y + containerBounds.y/2 + dateEntryHeight/2;
657 
658 	//Put Calendar above control if it would be cut off at the bottom.
659 	if (y + height > displayRect.y + displayRect.height) {
660 		y -= (height + dateEntryHeight);
661 	}
662 	return y;
663 }
664 
calculateCalendarXpos(Point calendarSize, Rectangle coordsRelativeToScreen, Rectangle displayRect, int width)665 private int calculateCalendarXpos (Point calendarSize, Rectangle coordsRelativeToScreen, Rectangle displayRect,
666 		int width) {
667 	Integer x;
668 	x = coordsRelativeToScreen.x;
669 	//Move calendar to the right if it would be cut off.
670 	if (x + width > displayRect.x + displayRect.width) {
671 		x = displayRect.x + displayRect.width - calendarSize.x;
672 	}
673 	return x;
674 }
675 
676 
focusDayOnPopupCalendar()677 private void focusDayOnPopupCalendar () {
678 	int currentYear = Calendar.getInstance ().get (Calendar.YEAR);
679 	int currentMonth = Calendar.getInstance ().get (Calendar.MONTH);
680 
681 	if (savedYear == currentYear && savedMonth == currentMonth) {
682 		int currentDay = Calendar.getInstance ().get (Calendar.DAY_OF_MONTH);
683 		GTK.gtk_calendar_mark_day (popupCalendar.handle, currentDay);
684 	}
685 }
686 
setCurrentDate()687 private void setCurrentDate () {
688 	savedYear = getYear ();
689 	savedMonth = getMonth ();
690 	savedDay = getDay ();
691 }
692 
recreateCalendar()693 private void recreateCalendar () {
694 	int year = popupCalendar.getYear ();
695 	int month = popupCalendar.getMonth ();
696 	int day = popupCalendar.getDay ();
697 	popupCalendar.removeListener (SWT.Dispose, popupListener);
698 	popupShell.dispose ();
699 	popupShell = null;
700 	popupCalendar = null;
701 	createPopupShell (year, month, day);
702 }
703 
hideDropDownCalendar()704 private void hideDropDownCalendar () {
705 	popupShell.setVisible (false);
706 	GTK.gtk_calendar_clear_marks (popupCalendar.handle);
707 	display.removeFilter (SWT.MouseDown, mouseEventListener);
708 	return;
709 }
710 
getComputeSizeString(int style)711 String getComputeSizeString (int style) {
712 	if ((style & SWT.DATE) != 0) {
713 		return (style & SWT.SHORT) != 0 ? DEFAULT_SHORT_DATE_FORMAT : (style & SWT.LONG) != 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT;
714 	}
715 	// SWT.TIME
716 	return (style & SWT.SHORT) != 0 ? DEFAULT_SHORT_TIME_FORMAT : (style & SWT.LONG) != 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT;
717 }
718 
getFormattedString()719 String getFormattedString() {
720 	return dateFormat.format(calendar.getTime());
721 }
722 
getDate()723 void getDate () {
724 	int [] y = new int [1];
725 	int [] m = new int [1];
726 	int [] d = new int [1];
727 
728 	if (GTK.GTK4) {
729 		long dateTime = GTK4.gtk_calendar_get_date(calendarHandle);
730 		OS.g_date_time_get_ymd(dateTime, y, m, d);
731 	} else {
732 		GTK3.gtk_calendar_get_date (calendarHandle, y, m, d);
733 	}
734 
735 	year = y[0];
736 	month = m[0];
737 	day = d[0];
738 }
739 
740 /**
741  * Returns the receiver's date, or day of the month.
742  * <p>
743  * The first day of the month is 1, and the last day depends on the month and year.
744  * </p>
745  *
746  * @return a positive integer beginning with 1
747  *
748  * @exception SWTException <ul>
749  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
750  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
751  * </ul>
752  */
getDay()753 public int getDay () {
754 	checkWidget ();
755 	if (isCalendar ()) {
756 		getDate ();
757 		return day;
758 	} else {
759 		return calendar.get (Calendar.DAY_OF_MONTH);
760 	}
761 }
762 
763 /**
764  * Returns the receiver's hours.
765  * <p>
766  * Hours is an integer between 0 and 23.
767  * </p>
768  *
769  * @return an integer between 0 and 23
770  *
771  * @exception SWTException <ul>
772  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
773  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
774  * </ul>
775  */
getHours()776 public int getHours () {
777 	checkWidget ();
778 	if (isCalendar ()) {
779 		return hours;
780 	} else {
781 		return calendar.get (Calendar.HOUR_OF_DAY);
782 	}
783 }
784 
785 /**
786  * Returns the receiver's minutes.
787  * <p>
788  * Minutes is an integer between 0 and 59.
789  * </p>
790  *
791  * @return an integer between 0 and 59
792  *
793  * @exception SWTException <ul>
794  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
795  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
796  * </ul>
797  */
getMinutes()798 public int getMinutes () {
799 	checkWidget ();
800 	if (isCalendar ()) {
801 		return minutes;
802 	} else {
803 		return calendar.get (Calendar.MINUTE);
804 	}
805 }
806 
807 /**
808  * Returns the receiver's month.
809  * <p>
810  * The first month of the year is 0, and the last month is 11.
811  * </p>
812  *
813  * @return an integer between 0 and 11
814  *
815  * @exception SWTException <ul>
816  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
817  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
818  * </ul>
819  */
getMonth()820 public int getMonth () {
821 	checkWidget ();
822 	if (isCalendar ()) {
823 		getDate ();
824 		return month;
825 	} else {
826 		return calendar.get (Calendar.MONTH);
827 	}
828 }
829 
830 @Override
getNameText()831 String getNameText () {
832 	if(calendar == null) {
833 		return "";
834 	}
835 	if (isTime ()) {
836 		return getHours () + ":" + getMinutes () + ":" + getSeconds ();
837 	} else {
838 		return (getMonth () + 1) + "/" + getDay () + "/" + getYear ();
839 	}
840 }
841 
842 /**
843  * Returns the receiver's seconds.
844  * <p>
845  * Seconds is an integer between 0 and 59.
846  * </p>
847  *
848  * @return an integer between 0 and 59
849  *
850  * @exception SWTException <ul>
851  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
852  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
853  * </ul>
854  */
getSeconds()855 public int getSeconds () {
856 	checkWidget ();
857 	if (isCalendar ()) {
858 		return seconds;
859 	} else {
860 		return calendar.get (Calendar.SECOND);
861 	}
862 }
863 
864 /*
865  * Returns a textual representation of the receiver,
866  * intended for speaking the text aloud.
867  */
getSpokenText()868 String getSpokenText() {
869 	if (isTime()) {
870 		return DateFormat.getTimeInstance(DateFormat.FULL).format(calendar.getTime());
871 	} else if (isDate()) {
872 		return DateFormat.getDateInstance(DateFormat.FULL).format(calendar.getTime());
873 	} else {
874 		Calendar cal = Calendar.getInstance();
875 		getDate();
876 		cal.set(year, month, day);
877 		return DateFormat.getDateInstance(DateFormat.FULL).format(cal.getTime());
878 	}
879 }
880 
881 /**
882  * Returns the receiver's year.
883  * <p>
884  * The first year is 1752 and the last year is 9999.
885  * </p>
886  *
887  * @return an integer between 1752 and 9999
888  *
889  * @exception SWTException <ul>
890  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
891  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
892  * </ul>
893  */
getYear()894 public int getYear () {
895 	checkWidget ();
896 	if (isCalendar ()) {
897 		getDate ();
898 		return year;
899 	} else {
900 		return calendar.get (Calendar.YEAR);
901 	}
902 }
903 
904 @Override
gtk_day_selected(long widget)905 long gtk_day_selected (long widget) {
906 	sendSelectionEvent ();
907 	return 0;
908 }
909 
910 @Override
gtk_day_selected_double_click(long widget)911 long gtk_day_selected_double_click (long widget) {
912 	sendSelectionEvent (SWT.DefaultSelection);
913 	return 0;
914 }
915 
916 @Override
gtk_month_changed(long widget)917 long gtk_month_changed (long widget) {
918 	sendSelectionEvent ();
919 	return 0;
920 }
921 
922 @Override
eventHandle()923 long eventHandle () {
924 	return dateTimeHandle ();
925 }
926 
927 @Override
focusHandle()928 long focusHandle () {
929 	return dateTimeHandle ();
930 }
931 
932 @Override
fontHandle()933 long fontHandle () {
934 	return dateTimeHandle ();
935 }
936 
dateTimeHandle()937 private long dateTimeHandle () {
938 	if (isCalendar () && calendarHandle != 0) {
939 		return calendarHandle;
940 	} else if ((isDate () || isTime ())) {
941 		if (GTK.GTK4) {
942 			return handle;
943 		} else {
944 			if (textEntryHandle != 0) return textEntryHandle;
945 		}
946 		return super.focusHandle ();
947 	} else {
948 		return super.focusHandle ();
949 	}
950 }
951 
952 @Override
hookEvents()953 void hookEvents () {
954 	super.hookEvents ();
955 	if (isCalendar ()) {
956 		hookEventsForCalendar ();
957 	} else {
958 		if ((style & SWT.DROP_DOWN) == 0 ) {
959 			hookEventsForDateTimeSpinner ();
960 		}
961 
962 		if (!GTK.GTK4) {
963 			int eventMask =	GDK.GDK_POINTER_MOTION_MASK | GDK.GDK_BUTTON_PRESS_MASK | GDK.GDK_BUTTON_RELEASE_MASK;
964 			GTK3.gtk_widget_add_events(textEntryHandle, eventMask);
965 
966 			if (OS.G_OBJECT_TYPE (textEntryHandle) == GTK3.GTK_TYPE_MENU ()) {
967 				hookEventsForMenu ();
968 			}
969 		}
970 	}
971 }
972 
973 
hookEventsForCalendar()974 final private void hookEventsForCalendar () {
975 	OS.g_signal_connect_closure(calendarHandle, OS.day_selected, display.getClosure(DAY_SELECTED), false);
976 
977 	if (GTK.GTK4) {
978 		OS.g_signal_connect_closure(calendarHandle, OS.next_month, display.getClosure(MONTH_CHANGED), false);
979 		OS.g_signal_connect_closure(calendarHandle, OS.next_year, display.getClosure(MONTH_CHANGED), false);
980 		OS.g_signal_connect_closure(calendarHandle, OS.prev_month, display.getClosure(MONTH_CHANGED), false);
981 		OS.g_signal_connect_closure(calendarHandle, OS.prev_year, display.getClosure(MONTH_CHANGED), false);
982 	} else {
983 		OS.g_signal_connect_closure(calendarHandle, OS.day_selected_double_click, display.getClosure(DAY_SELECTED_DOUBLE_CLICK), false);
984 		OS.g_signal_connect_closure(calendarHandle, OS.month_changed, display.getClosure(MONTH_CHANGED), false);
985 	}
986 }
987 
hookEventsForDateTimeSpinner()988 final private void hookEventsForDateTimeSpinner () {
989 	OS.g_signal_connect_closure (handle, OS.output, display.getClosure (OUTPUT), true);
990 	if (GTK.GTK4) {
991 		//TODO: GTK4 focus-in (focus event)?
992 	} else {
993 		OS.g_signal_connect_closure (textEntryHandle, OS.focus_in_event, display.getClosure (FOCUS_IN_EVENT), true);
994 	}
995 }
996 
hookEventsForMenu()997 final private void hookEventsForMenu () {
998 	OS.g_signal_connect_closure (down.handle, OS.selection_done, display.getClosure (SELECTION_DONE), true);
999 }
1000 
incrementField(int amount)1001 void incrementField(int amount) {
1002 	if (currentField != null) {
1003 		int field = getCalendarField(currentField);
1004 		if (field == Calendar.HOUR && hasAmPm()) {
1005 			int max = calendar.getMaximum(Calendar.HOUR);
1006 			int min = calendar.getMinimum(Calendar.HOUR);
1007 			int value = calendar.get(Calendar.HOUR);
1008 			if ((value == max && amount == 1) || (value == min && amount == -1)) {
1009 				calendar.roll(Calendar.AM_PM, amount);
1010 			}
1011 		}
1012 		if (field > -1) {
1013 			calendar.roll(field, amount);
1014 			updateControl();
1015 			selectField(updateField(currentField));
1016 		}
1017 	}
1018 }
1019 
hasAmPm()1020 private boolean hasAmPm() {
1021 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
1022 	while (iterator.current() != CharacterIterator.DONE) {
1023 		for (Attribute attribute : iterator.getAttributes().keySet()) {
1024 			if (Field.AM_PM.equals(attribute)) {
1025 				return true;
1026 			}
1027 		}
1028 		iterator.setIndex(iterator.getRunLimit());
1029 	}
1030 	return false;
1031 }
1032 
isDropped()1033 boolean isDropped () {
1034 	return popupShell.getVisible ();
1035 }
1036 
isCalendar()1037 private boolean isCalendar () {
1038 	return ((style & SWT.CALENDAR) != 0);
1039 }
1040 
isDateWithDropDownButton()1041 private boolean isDateWithDropDownButton () {
1042 	return ((style & SWT.DROP_DOWN) != 0 && (style & SWT.DATE) != 0);
1043 }
1044 
isDate()1045 private boolean isDate () {
1046 	return ((style & SWT.DATE) != 0);
1047 }
1048 
isTime()1049 private boolean isTime () {
1050 	return ((style & SWT.TIME) != 0);
1051 }
1052 
isReadOnly()1053 private boolean isReadOnly () {
1054 	return ((style & SWT.READ_ONLY) != 0);
1055 }
1056 
showWeekNumbers()1057 private boolean showWeekNumbers() {
1058 	return ((style & SWT.CALENDAR_WEEKNUMBERS) != 0);
1059 }
1060 
initAccessible()1061 void initAccessible () {
1062 	Accessible accessible = getAccessible ();
1063 	accessible.addAccessibleListener (new AccessibleAdapter () {
1064 		@Override
1065 		public void getName (AccessibleEvent e) {
1066 			e.result = getSpokenText ();
1067 		}
1068 
1069 		@Override
1070 		public void getHelp (AccessibleEvent e) {
1071 			e.result = getToolTipText ();
1072 		}
1073 	});
1074 
1075 	accessible.addAccessibleControlListener (new AccessibleControlAdapter () {
1076 		@Override
1077 		public void getChildAtPoint (AccessibleControlEvent e) {
1078 			e.childID = ACC.CHILDID_SELF;
1079 		}
1080 
1081 		@Override
1082 		public void getLocation (AccessibleControlEvent e) {
1083 			Rectangle rect = display.map (getParent (), null, getBounds ());
1084 			e.x = rect.x;
1085 			e.y = rect.y;
1086 			e.width = rect.width;
1087 			e.height = rect.height;
1088 		}
1089 
1090 		@Override
1091 		public void getChildCount (AccessibleControlEvent e) {
1092 			e.detail = 0;
1093 		}
1094 
1095 		@Override
1096 		public void getRole (AccessibleControlEvent e) {
1097 			e.detail = (isCalendar ()) ? ACC.ROLE_LABEL : ACC.ROLE_TEXT;
1098 		}
1099 
1100 		@Override
1101 		public void getState (AccessibleControlEvent e) {
1102 			e.detail = ACC.STATE_FOCUSABLE;
1103 			if (hasFocus ()) e.detail |= ACC.STATE_FOCUSED;
1104 		}
1105 
1106 		@Override
1107 		public void getSelection (AccessibleControlEvent e) {
1108 			if (hasFocus ()) e.childID = ACC.CHILDID_SELF;
1109 		}
1110 
1111 		@Override
1112 		public void getFocus (AccessibleControlEvent e) {
1113 			if (hasFocus ()) e.childID = ACC.CHILDID_SELF;
1114 		}
1115 	});
1116 }
1117 
isValidTime(int fieldName, int value)1118 boolean isValidTime (int fieldName, int value) {
1119 	Calendar validCalendar;
1120 	if (isCalendar ()) {
1121 		validCalendar = Calendar.getInstance ();
1122 	} else {
1123 		validCalendar = calendar;
1124 	}
1125 	int min = validCalendar.getActualMinimum (fieldName);
1126 	int max = validCalendar.getActualMaximum (fieldName);
1127 	return value >= min && value <= max;
1128 }
1129 
isValidDate(int year, int month, int day)1130 boolean isValidDate (int year, int month, int day) {
1131 	if (year < MIN_YEAR || year > MAX_YEAR) return false;
1132 	Calendar valid = Calendar.getInstance ();
1133 	valid.set (year, month, day);
1134 	return valid.get (Calendar.YEAR) == year
1135 		&& valid.get (Calendar.MONTH) == month
1136 		&& valid.get (Calendar.DAY_OF_MONTH) == day;
1137 }
1138 
popupCalendarEvent(Event event)1139 void popupCalendarEvent (Event event) {
1140 	switch (event.type) {
1141 		case SWT.Dispose:
1142 			if (popupShell != null && !popupShell.isDisposed () && !isDisposed () && getShell () != popupShell.getParent ()) {
1143 				int year = popupCalendar.getYear ();
1144 				int month = popupCalendar.getMonth ();
1145 				int day = popupCalendar.getDay ();
1146 				popupShell = null;
1147 				popupCalendar = null;
1148 				createPopupShell (year, month, day);
1149 			}
1150 			break;
1151 		case SWT.FocusIn: {
1152 			handleFocus (SWT.FocusIn);
1153 			break;
1154 		}
1155 		case SWT.MouseDown: {
1156 			if (event.button != 1) return;
1157 			mdYear = getYear();
1158 			mdMonth = getMonth();
1159 			break;
1160 		}
1161 		case SWT.MouseUp: {
1162 			if (event.button != 1) return;
1163 			/*
1164 			* The drop-down should stay visible when
1165 			* either the year or month is changed.
1166 			*/
1167 			if (mdYear == getYear() && mdMonth == getMonth()) {
1168 				dropDownCalendar (false);
1169 			}
1170 			break;
1171 		}
1172 		case SWT.Selection: {
1173 			int year = popupCalendar.getYear ();
1174 			int month = popupCalendar.getMonth ();
1175 			int day = popupCalendar.getDay ();
1176 			setDate (year, month, day);
1177 			Event e = new Event ();
1178 			e.time = event.time;
1179 			e.stateMask = event.stateMask;
1180 			e.doit = event.doit;
1181 			notifyListeners (SWT.Selection, e);
1182 			event.doit = e.doit;
1183 			break;
1184 		}
1185 		case SWT.Traverse: {
1186 			switch (event.detail) {
1187 				case SWT.TRAVERSE_RETURN:
1188 				case SWT.TRAVERSE_ESCAPE:
1189 				case SWT.TRAVERSE_ARROW_PREVIOUS:
1190 				case SWT.TRAVERSE_ARROW_NEXT:
1191 					event.doit = false;
1192 					break;
1193 				case SWT.TRAVERSE_TAB_NEXT:
1194 				case SWT.TRAVERSE_TAB_PREVIOUS:
1195 //					event.doit = text.traverse (event.detail);
1196 					event.detail = SWT.TRAVERSE_NONE;
1197 					if (event.doit) dropDownCalendar (false);
1198 					return;
1199 				case SWT.TRAVERSE_PAGE_NEXT:
1200 				case SWT.TRAVERSE_PAGE_PREVIOUS:
1201 					return;
1202 			}
1203 			Event e = new Event ();
1204 			e.time = event.time;
1205 			e.detail = event.detail;
1206 			e.doit = event.doit;
1207 			e.character = event.character;
1208 			e.keyCode = event.keyCode;
1209 			notifyListeners (SWT.Traverse, e);
1210 			event.doit = e.doit;
1211 			event.detail = e.detail;
1212 			break;
1213 		}
1214 		case SWT.KeyUp: {
1215 			Event e = new Event ();
1216 			e.time = event.time;
1217 			e.character = event.character;
1218 			e.keyCode = event.keyCode;
1219 			e.stateMask = event.stateMask;
1220 			notifyListeners (SWT.KeyUp, e);
1221 			break;
1222 		}
1223 		case SWT.KeyDown: {
1224 			if (event.character == SWT.ESC) {
1225 				/* Escape key cancels popupCalendar and reverts date */
1226 				popupCalendar.setDate (savedYear, savedMonth, savedDay);
1227 				setDate (savedYear, savedMonth, savedDay);
1228 				dropDownCalendar (false);
1229 			}
1230 			if (event.keyCode == SWT.CR || (event.stateMask & SWT.ALT) != 0 && (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN)) {
1231 				/* Return, Alt+Down, and Alt+Up cancel popupCalendar and select date. */
1232 				dropDownCalendar (false);
1233 			}
1234 			if (event.keyCode == SWT.SPACE) {
1235 				dropDownCalendar (false);
1236 			}
1237 			/* At this point the widget may have been disposed.
1238 			 * If so, do not continue. */
1239 			if (isDisposed ()) break;
1240 			Event e = new Event ();
1241 			e.time = event.time;
1242 			e.character = event.character;
1243 			e.keyCode = event.keyCode;
1244 			e.stateMask = event.stateMask;
1245 			notifyListeners (SWT.KeyDown, e);
1246 			break;
1247 		}
1248 	}
1249 }
1250 
handleFocus(int type)1251 void handleFocus (int type) {
1252 	if (isDisposed ()) return;
1253 	switch (type) {
1254 		case SWT.FocusIn: {
1255 			if (hasFocus) return;
1256 			selectAll ();
1257 			hasFocus = true;
1258 			Shell shell = getShell ();
1259 			shell.removeListener (SWT.Deactivate, popupListener);
1260 			shell.addListener (SWT.Deactivate, popupListener);
1261 			Display display = getDisplay ();
1262 			display.removeFilter (SWT.FocusIn, popupFilter);
1263 			Event e = new Event ();
1264 			notifyListeners (SWT.FocusIn, e);
1265 			break;
1266 		}
1267 		case SWT.FocusOut: {
1268 			if (!hasFocus) return;
1269 			Control focusControl = getDisplay ().getFocusControl ();
1270 			if (focusControl == down || focusControl == popupCalendar ) return;
1271 			hasFocus = false;
1272 			Shell shell = getShell ();
1273 			shell.removeListener (SWT.Deactivate, popupListener);
1274 			Display display = getDisplay ();
1275 			display.removeFilter (SWT.MouseDown, mouseEventListener);
1276 			Event e = new Event ();
1277 			notifyListeners (SWT.FocusOut, e);
1278 			break;
1279 		}
1280 	}
1281 }
1282 
popupShellEvent(Event event)1283 void popupShellEvent (Event event) {
1284 	switch (event.type) {
1285 		case SWT.Close:
1286 			event.doit = false;
1287 			dropDownCalendar (false);
1288 			break;
1289 		case SWT.MouseUp:
1290 			dropDownCalendar (false);
1291 			break;
1292 	}
1293 }
1294 
1295 /**
1296  * Removes the listener from the collection of listeners who will
1297  * be notified when the control is selected by the user.
1298  *
1299  * @param listener the listener which should no longer be notified
1300  *
1301  * @exception IllegalArgumentException <ul>
1302  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1303  * </ul>
1304  * @exception SWTException <ul>
1305  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1306  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1307  * </ul>
1308  *
1309  * @see SelectionListener
1310  * @see #addSelectionListener
1311  */
removeSelectionListener(SelectionListener listener)1312 public void removeSelectionListener (SelectionListener listener) {
1313 	checkWidget ();
1314 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
1315 	if (eventTable == null) return;
1316 	eventTable.unhook (SWT.Selection, listener);
1317 	eventTable.unhook (SWT.DefaultSelection, listener);
1318 }
1319 
1320 /**
1321  * selects the first occurrence of the given field
1322  *
1323  * @param field
1324  */
selectField(Field field)1325 void selectField(Field field) {
1326 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
1327 	while (iterator.current() != CharacterIterator.DONE) {
1328 		for (Attribute attribute : iterator.getAttributes().keySet()) {
1329 			if (attribute.equals(field)) {
1330 				selectField(getFieldPosition(field, iterator));
1331 				return;
1332 			}
1333 		}
1334 		iterator.setIndex(iterator.getRunLimit());
1335 	}
1336 }
1337 
1338 /**
1339  * Selects the given field at the given start/end coordinates
1340  *
1341  * @param field
1342  * @param start
1343  * @param end
1344  */
selectField(FieldPosition fieldPosition)1345 void selectField(FieldPosition fieldPosition) {
1346 	boolean sameField = isSameField(fieldPosition, currentField);
1347 	if (sameField) {
1348 		if (typeBufferPos > -1) {
1349 			typeBufferPos = 0;
1350 		}
1351 	} else {
1352 		typeBufferPos = -1;
1353 		commitData();
1354 		fieldPosition = updateField(fieldPosition);
1355 	}
1356 	Point pt = getTextSelection();
1357 	int start = fieldPosition.getBeginIndex();
1358 	int end = fieldPosition.getEndIndex();
1359 	if (sameField && start == pt.x && end == pt.y) {
1360 		return;
1361 	}
1362 	currentField = fieldPosition;
1363 	display.syncExec(() -> {
1364 		if (textEntryHandle != 0) {
1365 			String value = getText(getText(), start, end - 1);
1366 			int s = value.lastIndexOf(' ');
1367 			s = (s == -1) ? start : start + s + 1;
1368 			setTextSelection(s, end);
1369 		}
1370 	});
1371 	sendSelectionEvent(SWT.Selection);
1372 }
1373 
sendSelectionEvent()1374 void sendSelectionEvent () {
1375 	int [] y = new int [1];
1376 	int [] m = new int [1];
1377 	int [] d = new int [1];
1378 
1379 	if (GTK.GTK4) {
1380 		long dateTime = GTK4.gtk_calendar_get_date(calendarHandle);
1381 		OS.g_date_time_get_ymd(dateTime, y, m, d);
1382 	} else {
1383 		GTK3.gtk_calendar_get_date (calendarHandle, y, m, d);
1384 	}
1385 
1386 	if (d[0] != day ||
1387 		m[0] != month ||
1388 		y[0] != year) {
1389 		year = y[0];
1390 		month = m[0];
1391 		day = d[0];
1392 		/* Highlight the current (today) date */
1393 		if (year == Calendar.getInstance ().get (Calendar.YEAR) && month == Calendar.getInstance ().get (Calendar.MONTH)) {
1394 			GTK.gtk_calendar_mark_day (calendarHandle, Calendar.getInstance ().get (Calendar.DAY_OF_MONTH));
1395 		} else {
1396 			GTK.gtk_calendar_clear_marks (calendarHandle);
1397 		}
1398 		sendSelectionEvent (SWT.Selection);
1399 	}
1400 }
1401 
1402 @Override
setBackground(Color color)1403 public void setBackground (Color color) {
1404 	super.setBackground (color);
1405 	bg = color;
1406 	if (popupCalendar != null) popupCalendar.setBackground (color);
1407 }
1408 
1409 @Override
setBackgroundGdkRGBA(GdkRGBA rgba)1410 void setBackgroundGdkRGBA (GdkRGBA rgba) {
1411 	super.setBackgroundGdkRGBA(rgba);
1412 	if (calendarHandle != 0) {
1413 		setBackgroundGdkRGBA (calendarHandle, rgba);
1414 	}
1415 	super.setBackgroundGdkRGBA(rgba);
1416 
1417 }
1418 
1419 @Override
setBackgroundGdkRGBA(long context, long handle, GdkRGBA rgba)1420 void setBackgroundGdkRGBA (long context, long handle, GdkRGBA rgba) {
1421 	// We need to override here because DateTime widgets use "background" instead of
1422 	// "background-color" as their CSS property.
1423 
1424 	// Form background string
1425 	String name = display.gtk_widget_class_get_css_name(handle);
1426 	String css = name + " {background: " + display.gtk_rgba_to_css_string (rgba) + ";}\n" +
1427 			name + ":selected" + " {background: " + display.gtk_rgba_to_css_string(display.COLOR_LIST_SELECTION_RGBA) + ";}";
1428 
1429 	// Cache background
1430 	cssBackground = css;
1431 
1432 	// Apply background color and any cached foreground color
1433 	String finalCss = display.gtk_css_create_css_color_string (cssBackground, cssForeground, SWT.BACKGROUND);
1434 	gtk_css_provider_load_from_css (context, finalCss);
1435 }
1436 
1437 @Override
setEnabled(boolean enabled)1438 public void setEnabled (boolean enabled){
1439 	super.setEnabled (enabled);
1440 	if (isDateWithDropDownButton ())
1441 		down.setEnabled (enabled);
1442 }
1443 
1444 @Override
setFont(Font font)1445 public void setFont (Font font) {
1446 	super.setFont (font);
1447 	this.font = font;
1448 	if (popupCalendar != null) popupCalendar.setFont (font);
1449 	redraw ();
1450 }
1451 
1452 @Override
setForegroundGdkRGBA(GdkRGBA rgba)1453 void setForegroundGdkRGBA (GdkRGBA rgba) {
1454 	setForegroundGdkRGBA (containerHandle, rgba);
1455 }
1456 
1457 @Override
setForeground(Color color)1458 public void setForeground (Color color) {
1459 	super.setForeground (color);
1460 	fg = color;
1461 	if (popupCalendar != null) popupCalendar.setForeground (color);
1462 }
1463 
setFieldOfInternalDataStructure(FieldPosition field, int value)1464 void setFieldOfInternalDataStructure(FieldPosition field, int value) {
1465 	int calendarField = getCalendarField(field);
1466 	if (calendar.get(calendarField) == value)
1467 		return;
1468 	if (calendarField == Calendar.AM_PM && hasAmPm()) {
1469 		calendar.roll(Calendar.HOUR_OF_DAY, 12);
1470 	}
1471 	calendar.set(calendarField, value);
1472 
1473 	//When dealing with months with 31 days and have days set to 31, then if you change the month
1474 	//to one that has 30 (or less) days, then in calendar only the day is changed but the month stays.
1475 	//e.g 10.31.2014  -> decrement month, becomes:
1476 	//    10.01.2014.
1477 	//To get around this behaviour, we set the field again.
1478 	if (calendar.get(calendarField) != value) {
1479 		calendar.set(calendarField, value);
1480 	}
1481 	sendSelectionEvent (SWT.Selection);
1482 }
1483 
1484 /**
1485  * Sets the receiver's year, month, and day in a single operation.
1486  * <p>
1487  * This is the recommended way to set the date, because setting the year,
1488  * month, and day separately may result in invalid intermediate dates.
1489  * </p>
1490  *
1491  * @param year an integer between 1752 and 9999
1492  * @param month an integer between 0 and 11
1493  * @param day a positive integer beginning with 1
1494  *
1495  * @exception SWTException <ul>
1496  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1497  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1498  * </ul>
1499  *
1500  * @since 3.4
1501  */
setDate(int year, int month, int day)1502 public void setDate (int year, int month, int day) {
1503 	checkWidget ();
1504 	if (!isValidDate (year, month, day)) return;
1505 	if (isCalendar ()) {
1506 		this.year = year;
1507 		this.month = month;
1508 		this.day = day;
1509 
1510 		if (GTK.GTK4) {
1511 			long dateTime = OS.g_date_time_new_local(year, month + 1, day, 0, 0, 0);
1512 			GTK4.gtk_calendar_select_day(calendarHandle, dateTime);
1513 		} else {
1514 			GTK3.gtk_calendar_select_month (calendarHandle, month, year);
1515 			GTK3.gtk_calendar_select_day (calendarHandle, day);
1516 		}
1517 	} else {
1518 		calendar.set (year, month, day);
1519 		updateControl ();
1520 	}
1521 }
1522 
1523 /**
1524  * Sets the receiver's date, or day of the month, to the specified day.
1525  * <p>
1526  * The first day of the month is 1, and the last day depends on the month and year.
1527  * If the specified day is not valid for the receiver's month and year, then it is ignored.
1528  * </p>
1529  *
1530  * @param day a positive integer beginning with 1
1531  *
1532  * @exception SWTException <ul>
1533  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1534  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1535  * </ul>
1536  *
1537  * @see #setDate
1538  */
setDay(int day)1539 public void setDay (int day) {
1540 	checkWidget ();
1541 	if (!isValidDate (getYear (), getMonth (), day)) return;
1542 	if (isCalendar ()) {
1543 		this.day = day;
1544 
1545 		if (GTK.GTK4) {
1546 			long dateTime = OS.g_date_time_new_local(this.year, this.month + 1, day, 0, 0, 0);
1547 			GTK4.gtk_calendar_select_day(calendarHandle, dateTime);
1548 		} else {
1549 			GTK3.gtk_calendar_select_day (calendarHandle, day);
1550 		}
1551 	} else {
1552 		calendar.set (Calendar.DAY_OF_MONTH, day);
1553 		updateControl ();
1554 	}
1555 }
1556 
1557 /**
1558  * Sets the receiver's hours.
1559  * <p>
1560  * Hours is an integer between 0 and 23.
1561  * </p>
1562  *
1563  * @param hours an integer between 0 and 23
1564  *
1565  * @exception SWTException <ul>
1566  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1567  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1568  * </ul>
1569  */
setHours(int hours)1570 public void setHours (int hours) {
1571 	checkWidget ();
1572 	if (!isValidTime (Calendar.HOUR_OF_DAY, hours)) return;
1573 	if (isCalendar ()) {
1574 		this.hours = hours;
1575 	} else {
1576 		calendar.set (Calendar.HOUR_OF_DAY, hours);
1577 		updateControl ();
1578 	}
1579 }
1580 
1581 @Override
setMenu(Menu menu)1582 public void setMenu (Menu menu) {
1583 	super.setMenu (menu);
1584 	if (down != null) down.setMenu (menu);
1585 }
1586 
1587 /**
1588  * Sets the receiver's minutes.
1589  * <p>
1590  * Minutes is an integer between 0 and 59.
1591  * </p>
1592  *
1593  * @param minutes an integer between 0 and 59
1594  *
1595  * @exception SWTException <ul>
1596  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1597  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1598  * </ul>
1599  */
setMinutes(int minutes)1600 public void setMinutes (int minutes) {
1601 	checkWidget ();
1602 	if (!isValidTime (Calendar.MINUTE, minutes)) return;
1603 	if (isCalendar ()) {
1604 		this.minutes = minutes;
1605 	} else {
1606 		calendar.set (Calendar.MINUTE, minutes);
1607 		updateControl ();
1608 	}
1609 }
1610 
1611 /**
1612  * Sets the receiver's month.
1613  * <p>
1614  * The first month of the year is 0, and the last month is 11.
1615  * If the specified month is not valid for the receiver's day and year, then it is ignored.
1616  * </p>
1617  *
1618  * @param month an integer between 0 and 11
1619  *
1620  * @exception SWTException <ul>
1621  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1622  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1623  * </ul>
1624  *
1625  * @see #setDate
1626  */
setMonth(int month)1627 public void setMonth (int month) {
1628 	checkWidget ();
1629 	if (!isValidDate (getYear (), month, getDay ())) return;
1630 	if (isCalendar ()) {
1631 		this.month = month;
1632 		GTK3.gtk_calendar_select_month (calendarHandle, month, year);
1633 	} else {
1634 		calendar.set (Calendar.MONTH, month);
1635 		updateControl ();
1636 	}
1637 }
1638 
1639 /**
1640  * Sets the receiver's seconds.
1641  * <p>
1642  * Seconds is an integer between 0 and 59.
1643  * </p>
1644  *
1645  * @param seconds an integer between 0 and 59
1646  *
1647  * @exception SWTException <ul>
1648  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1649  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1650  * </ul>
1651  */
setSeconds(int seconds)1652 public void setSeconds (int seconds) {
1653 	checkWidget ();
1654 	if (!isValidTime (Calendar.SECOND, seconds)) return;
1655 	if (isCalendar ()) {
1656 		this.seconds = seconds;
1657 	} else {
1658 		calendar.set (Calendar.SECOND, seconds);
1659 		updateControl ();
1660 	}
1661 }
1662 
1663 /**
1664  * Sets the receiver's hours, minutes, and seconds in a single operation.
1665  *
1666  * @param hours an integer between 0 and 23
1667  * @param minutes an integer between 0 and 59
1668  * @param seconds an integer between 0 and 59
1669  *
1670  * @exception SWTException <ul>
1671  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1672  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1673  * </ul>
1674  *
1675  * @since 3.4
1676  */
setTime(int hours, int minutes, int seconds)1677 public void setTime (int hours, int minutes, int seconds) {
1678 	checkWidget ();
1679 	if (!isValidTime (Calendar.HOUR_OF_DAY, hours)) return;
1680 	if (!isValidTime (Calendar.MINUTE, minutes)) return;
1681 	if (!isValidTime (Calendar.SECOND, seconds)) return;
1682 	if (isCalendar ()) {
1683 		this.hours = hours;
1684 		this.minutes = minutes;
1685 		this.seconds = seconds;
1686 	} else {
1687 		calendar.set (Calendar.HOUR_OF_DAY, hours);
1688 		calendar.set (Calendar.MINUTE, minutes);
1689 		calendar.set (Calendar.SECOND, seconds);
1690 		updateControl ();
1691 	}
1692 }
1693 
1694 /**
1695  * Sets the receiver's year.
1696  * <p>
1697  * The first year is 1752 and the last year is 9999.
1698  * If the specified year is not valid for the receiver's day and month, then it is ignored.
1699  * </p>
1700  *
1701  * @param year an integer between 1752 and 9999
1702  *
1703  * @exception SWTException <ul>
1704  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1705  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1706  * </ul>
1707  *
1708  * @see #setDate
1709  */
setYear(int year)1710 public void setYear (int year) {
1711 	checkWidget ();
1712 	if (!isValidDate (year, getMonth (), getDay ())) return;
1713 	if (isCalendar ()) {
1714 		this.year = year;
1715 		GTK3.gtk_calendar_select_month (calendarHandle, month, year);
1716 	} else {
1717 		calendar.set (Calendar.YEAR, year);
1718 		updateControl ();
1719 	}
1720 }
1721 
1722 @Override
setBoundsInPixels(int x, int y, int width, int height)1723 void setBoundsInPixels (int x, int y, int width, int height) {
1724 
1725 	//Date with Drop down is in container. Needs extra handling.
1726 	if (isDateWithDropDownButton ()) {
1727 		long sizingHandle = textEntryHandle;
1728 		GtkRequisition requisition = new GtkRequisition ();
1729 		GTK.gtk_widget_get_preferred_size (sizingHandle, null, requisition);
1730 		int oldHeight = requisition.height; //Entry should not expand vertically. It is single liner.
1731 
1732 		int newWidth = width - (down.getSizeInPixels ().x + getGtkBorderPadding ().right);
1733 		GTK.gtk_widget_set_size_request (sizingHandle, (newWidth >= 0) ? newWidth : 0, oldHeight);
1734 	}
1735 
1736 	super.setBoundsInPixels (x, y, width, height);
1737 }
1738 
1739 /**
1740  * Usually called when control is resized or first initialized.
1741  */
setDropDownButtonSize()1742 private void setDropDownButtonSize () {
1743 	Rectangle rect = getClientAreaInPixels ();
1744 	int parentWidth = rect.width;
1745 	int parentHeight = rect.height;
1746 	Point buttonSize = down.computeSizeInPixels (SWT.DEFAULT, parentHeight);
1747 
1748 	//TAG_GTK3__NO_VERTICAL_FILL_ADJUSTMENT
1749 	int dateEntryHeight = computeNativeSize (textEntryHandle, SWT.DEFAULT, SWT.DEFAULT, false).y;
1750 	int newHeight = dateEntryHeight;
1751 
1752 	//Move button a little closer to entry field, by amount of padding.
1753 	int newXpos = parentWidth - buttonSize.x - getGtkBorderPadding().left - getGtkBorderPadding().right;
1754 
1755 	int newYPos = parentHeight/2 - dateEntryHeight/2;
1756 	down.setBoundsInPixels (newXpos, newYPos, buttonSize.x, newHeight);
1757 }
1758 
1759 /**
1760  * Gets the border padding structure, which can be used to determine the inner padding of the text field.
1761  * Note, this function returns the correct padding only under GTK3.
1762  * Under Gtk2, it returns a constant.
1763  * @return GtkBorder object that holds the padding values.
1764  */
getGtkBorderPadding()1765 GtkBorder getGtkBorderPadding () {
1766 	//In Gtk3, acquire border.
1767 	GtkBorder gtkBorderPadding = new GtkBorder ();
1768 	long contextHandle = textEntryHandle;
1769 	long context = GTK.gtk_widget_get_style_context (contextHandle);
1770 	int state_flag = GTK.gtk_widget_get_state_flags(contextHandle);
1771 	gtk_style_context_get_padding(context, state_flag, gtkBorderPadding);
1772 	return gtkBorderPadding;
1773 }
1774 
onNumberKeyInput(int key)1775 boolean onNumberKeyInput(int key) {
1776 	if (currentField == null) {
1777 		return false;
1778 	}
1779 	int fieldName = getCalendarField(currentField);
1780 	StringBuilder prefix = new StringBuilder();
1781 	StringBuilder current = new StringBuilder();
1782 	StringBuilder suffix = new StringBuilder();
1783 
1784 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
1785 	char c = iterator.first();
1786 	do {
1787 		if (isSameField(currentField, getFieldPosition(iterator))) {
1788 			current.append(c);
1789 		} else if (current.length() == 0) {
1790 			prefix.append(c);
1791 		} else {
1792 			suffix.append(c);
1793 		}
1794 	} while ((c = iterator.next()) != CharacterIterator.DONE);
1795 
1796 	if (typeBufferPos < 0) {
1797 		typeBuffer.setLength(0);
1798 		typeBuffer.append(current);
1799 		typeBufferPos = 0;
1800 	}
1801 	if (key == GDK.GDK_BackSpace) {
1802 		if (typeBufferPos > 0 && typeBufferPos <= typeBuffer.length()) {
1803 			typeBuffer.deleteCharAt(typeBufferPos - 1);
1804 			typeBufferPos--;
1805 		}
1806 	} else if (key == GDK.GDK_Delete) {
1807 		if (typeBufferPos >= 0 && typeBufferPos < typeBuffer.length()) {
1808 			typeBuffer.deleteCharAt(typeBufferPos);
1809 		}
1810 	} else {
1811 		char newText = keyToString(key);
1812 		// Don't allow non-digit character inputs for SWT.TIME, unless modifying the AM/PM field
1813 		if ((style & SWT.TIME) != 0 && fieldName != Calendar.AM_PM && !Character.isDigit(newText)) {
1814 			return false;
1815 		}
1816 		if (!Character.isAlphabetic(newText) && !Character.isDigit(newText)) {
1817 			return false;
1818 		}
1819 		if (fieldName == Calendar.AM_PM) {
1820 			if (dateFormat instanceof SimpleDateFormat) {
1821 				String[] amPmStrings = ((SimpleDateFormat) dateFormat).getDateFormatSymbols().getAmPmStrings();
1822 				if (amPmStrings[Calendar.AM].charAt(0) == newText) {
1823 					setTextField(currentField, Calendar.AM);
1824 					return false;
1825 				} else if (amPmStrings[Calendar.PM].charAt(0) == newText) {
1826 					setTextField(currentField, Calendar.PM);
1827 					return false;
1828 				}
1829 			}
1830 		}
1831 		if (typeBufferPos < typeBuffer.length()) {
1832 			typeBuffer.replace(typeBufferPos, typeBufferPos + 1, Character.toString(newText));
1833 		} else {
1834 			typeBuffer.append(newText);
1835 		}
1836 		typeBufferPos++;
1837 	}
1838 	StringBuilder newText = new StringBuilder(prefix);
1839 	newText.append(typeBuffer);
1840 	newText.append(suffix);
1841 	setText(newText.toString());
1842 	setTextSelection(prefix.length() + typeBufferPos, prefix.length() + typeBuffer.length());
1843 	currentField.setBeginIndex(prefix.length());
1844 	currentField.setEndIndex(prefix.length() + typeBuffer.length());
1845 	if (!isCalendar()) {
1846 		try {
1847 			Date date = dateFormat.parse(getText());
1848 			calendar.setTime(date);
1849 		} catch (ParseException e) {
1850 			// invalid value, input will reset...
1851 		}
1852 	}
1853 	return false;
1854 }
1855 
keyToString(int key)1856 private char keyToString(int key) {
1857 	// If numberpad keys were pressed.
1858 	if (key >= GDK.GDK_KP_0 && key <= GDK.GDK_KP_9) {
1859 		// convert numberpad button to regular key;
1860 		key -= 65408;
1861 	}
1862 	return (char) GDK.gdk_keyval_to_unicode(key);
1863 }
1864 
updateControl()1865 void updateControl() {
1866 	if ((isDate() || isTime()) && textEntryHandle != 0) {
1867 		setText(getFormattedString());
1868 	}
1869 	redraw ();
1870 }
1871 
1872 @Override
register()1873 void register () {
1874 	super.register ();
1875 	if (handle != 0 && display.getWidget(handle) == null) display.addWidget(handle, this);
1876 	if (containerHandle != 0 && containerHandle != handle) display.addWidget (containerHandle, this);
1877 	if (textEntryHandle != 0 && textEntryHandle != containerHandle) display.addWidget (textEntryHandle, this);
1878 }
1879 
1880 @Override
defaultBackground()1881 GdkRGBA defaultBackground () {
1882 	return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).handle;
1883 }
1884 
1885 @Override
deregister()1886 void deregister () {
1887 	super.deregister ();
1888 	if (handle != 0 && display.getWidget(handle) != null) display.removeWidget(handle);
1889 	if (containerHandle != 0 && containerHandle != handle) display.removeWidget (containerHandle);
1890 	if (textEntryHandle != 0 && textEntryHandle != containerHandle) display.removeWidget (textEntryHandle);
1891 }
1892 
getArrow(long widget)1893 int getArrow(long widget) {
1894 	updateControl();
1895 	int adj_value = (int) GTK.gtk_adjustment_get_value(GTK.gtk_spin_button_get_adjustment(widget));
1896 	int new_value = 0;
1897 	if (isDate()) {
1898 		FieldPosition firstField = getNextField(null);
1899 		int firstFieldConstant = getCalendarField(firstField);
1900 		new_value = calendar.get(getCalendarField(firstField));
1901 		if (firstFieldConstant == Calendar.MONTH) {
1902 			if ((style & SWT.SHORT) != 0) {
1903 				// adj_value returns the month as a number between 1-12
1904 				// new_value gets the month as a number between 0-11
1905 				// shift the adj_value by offset so that we get the correct arrow direction
1906 				adj_value--;
1907 			} else if ((style & SWT.MEDIUM) != 0 || (style & SWT.LONG) != 0 ) {
1908 				// adj_value is either +1, 0, -1 when month is displayed as string
1909 				if (adj_value == 0) {
1910 					return 0;
1911 				} else {
1912 					return adj_value > 0 ? SWT.ARROW_UP : SWT.ARROW_DOWN;
1913 				}
1914 			}
1915 		}
1916 	} else if (isTime()) {
1917 		new_value = getHours();
1918 		if (hasAmPm()) {
1919 			// as getHours () has 24h format but spinner 12h format, new_value needs to be
1920 			// converted to 12h format
1921 			if (getHours() > 12) {
1922 				new_value = getHours() - 12;
1923 			}
1924 			if (new_value == 0)
1925 				new_value = 12;
1926 		}
1927 	}
1928 	if (adj_value == 0 && firstTime)
1929 		return 0;
1930 	firstTime = false;
1931 	if (adj_value == new_value)
1932 		return 0;
1933 	return adj_value > new_value ? SWT.ARROW_UP : SWT.ARROW_DOWN;
1934 }
1935 
1936 /**
1937  * Calculates appropriate width of GtkEntry and
1938  * adds Date/Time string to the Date/Time Spinner
1939  */
setText(String dateTimeText)1940 void setText (String dateTimeText) {
1941 	if (dateTimeText != null){
1942 		byte [] dateTimeConverted = Converter.wcsToMbcs (dateTimeText, true);
1943 
1944 		if (GTK.GTK4) {
1945 			long bufferHandle = isDateWithDropDownButton() ? GTK4.gtk_entry_get_buffer(textEntryHandle) : GTK4.gtk_text_get_buffer(textEntryHandle);
1946 			GTK.gtk_entry_buffer_set_text(bufferHandle, dateTimeConverted, dateTimeText.length());
1947 		} else {
1948 			//note, this is ignored if the control is in a fill-layout.
1949 			GTK3.gtk_entry_set_width_chars(textEntryHandle, dateTimeText.length());
1950 			GTK3.gtk_entry_set_text(textEntryHandle, dateTimeConverted);
1951 		}
1952 
1953 		if (popupCalendar != null && calendar != null) {
1954 			Date parse;
1955 			try {
1956 				parse = dateFormat.parse(dateTimeText);
1957 			} catch(ParseException e) {
1958 				//not a valid date (yet), return for now
1959 				return;
1960 			}
1961 			Calendar clone = (Calendar) calendar.clone();
1962 			clone.setTime(parse);
1963 			try {
1964 				popupCalendar.setDate (clone.get(Calendar.YEAR), clone.get(Calendar.MONTH), clone.get(Calendar.DAY_OF_MONTH));
1965 			} catch(SWTException e) {
1966 				if (e.code == SWT.ERROR_WIDGET_DISPOSED) {
1967 					//the calendar popup was disposed in the meantime so nothing to update
1968 					return;
1969 				}
1970 				throw e;
1971 			}
1972 		}
1973 	}
1974 }
1975 
1976 @Override
gtk_key_press_event(long widget, long event)1977 long gtk_key_press_event (long widget, long event) {
1978 	if (!isReadOnly () && (isTime () || isDate ())) {
1979 		int [] key = new int[1];
1980 		if (GTK.GTK4) {
1981 			key[0] = GDK.gdk_key_event_get_keyval(event);
1982 		} else {
1983 			GDK.gdk_event_get_keyval(event, key);
1984 		}
1985 
1986 		switch (key[0]) {
1987 		case GDK.GDK_Up:
1988 		case GDK.GDK_KP_Up:
1989 			incrementField(+1);
1990 			commitData();
1991 			break;
1992 		case GDK.GDK_Down:
1993 		case GDK.GDK_KP_Down:
1994 			incrementField(-1);
1995 			commitData();
1996 			break;
1997 		case GDK.GDK_Tab:
1998 		case GDK.GDK_Right:
1999 		case GDK.GDK_KP_Right:
2000 			selectField(getNextField(currentField));
2001 			sendEvent(SWT.Traverse);
2002 			break;
2003 		case GDK.GDK_Left:
2004 		case GDK.GDK_KP_Left:
2005 			selectField(getPreviousField(currentField));
2006 			sendEvent(SWT.Traverse);
2007 			break;
2008 		case GDK.GDK_Home:
2009 		case GDK.GDK_KP_Home:
2010 			/* Set the value of the current field to its minimum */
2011 			if (currentField != null) {
2012 				setTextField(currentField, calendar.getActualMinimum(getCalendarField(currentField)));
2013 			}
2014 			break;
2015 		case GDK.GDK_End:
2016 		case GDK.GDK_KP_End:
2017 			/* Set the value of the current field to its maximum */
2018 			if (currentField != null) {
2019 				setTextField(currentField, calendar.getActualMaximum(getCalendarField(currentField)));
2020 			}
2021 			break;
2022 		default:
2023 			onNumberKeyInput(key[0]);
2024 		}
2025 	}
2026 	return 1;
2027 }
2028 
commitData()2029 void commitData() {
2030 	try {
2031 		Date date = dateFormat.parse(getText());
2032 		calendar.setTime(date);
2033 	} catch (ParseException e) {
2034 		// invalid value, input will reset...
2035 	}
2036 	updateControl();
2037 }
2038 
2039 /**
2040  * Returns a string containing a copy of the contents of the
2041  * receiver's text field, or an empty string if there are no
2042  * contents.
2043  *
2044  * @return Spinner's text
2045  *
2046  * @exception SWTException <ul>
2047  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2048  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2049  * </ul>
2050  */
getText()2051 String getText() {
2052 	checkWidget();
2053 	if (textEntryHandle != 0) {
2054 		long stringPtr;
2055 
2056 		if (GTK.GTK4) {
2057 			long bufferHandle = isDateWithDropDownButton() ? GTK4.gtk_entry_get_buffer(textEntryHandle) : GTK4.gtk_text_get_buffer(textEntryHandle);
2058 			stringPtr = GTK.gtk_entry_buffer_get_text(bufferHandle);
2059 		} else {
2060 			stringPtr = GTK3.gtk_entry_get_text(textEntryHandle);
2061 		}
2062 
2063 		if (stringPtr == 0) return "";
2064 		int length = C.strlen(stringPtr);
2065 		byte[] buffer = new byte[length];
2066 		C.memmove(buffer, stringPtr, length);
2067 		return new String(Converter.mbcsToWcs(buffer));
2068 	} else {
2069 		return "";
2070 	}
2071 }
2072 
2073 /**
2074  * returns GtkEntry starting from index and ending with index
2075  * provided by the user
2076  */
getText(String str,int start, int end)2077 String getText (String str,int start, int end) {
2078 	checkWidget ();
2079 	if (!(start <= end && 0 <= end)) return "";
2080 	int length = str.length ();
2081 	end = Math.min (end, length - 1);
2082 	if (start > end) return "";
2083 	start = Math.max (0, start);
2084 	/*
2085 	* NOTE: The current implementation uses substring ()
2086 	* which can reference a potentially large character
2087 	* array.
2088 	*/
2089 	return str.substring (start, end + 1);
2090 }
2091 
2092 /**
2093  * Returns the selected text. If selected text is less than
2094  * one character the returned Point has equal start and end values.
2095  *
2096  * @return the highlighted (selected) text or position of cursor
2097  **/
getTextSelection()2098 Point getTextSelection() {
2099 	checkWidget();
2100 	int[] start = new int[1];
2101 	int[] end = new int[1];
2102 	GTK.gtk_editable_get_selection_bounds(textEntryHandle, start, end);
2103 
2104 	long stringPtr;
2105 	if (GTK.GTK4) {
2106 		long bufferHandle = isDateWithDropDownButton() ? GTK4.gtk_entry_get_buffer(textEntryHandle) : GTK4.gtk_text_get_buffer(textEntryHandle);
2107 		stringPtr = GTK.gtk_entry_buffer_get_text(bufferHandle);
2108 	} else {
2109 		stringPtr = GTK3.gtk_entry_get_text (textEntryHandle);
2110 	}
2111 
2112 	start[0] = (int)OS.g_utf8_offset_to_utf16_offset(stringPtr, start[0]);
2113 	end[0] = (int)OS.g_utf8_offset_to_utf16_offset(stringPtr, end[0]);
2114 
2115 	return new Point(start[0], end[0]);
2116 }
2117 
2118 /** Highlight (select) the text between the start and end. **/
setTextSelection(int start, int end)2119 void setTextSelection(int start, int end) {
2120 	checkWidget();
2121 
2122 	long stringPtr;
2123 	if (GTK.GTK4) {
2124 		long bufferHandle = isDateWithDropDownButton() ? GTK4.gtk_entry_get_buffer(textEntryHandle) : GTK4.gtk_text_get_buffer(textEntryHandle);
2125 		stringPtr = GTK.gtk_entry_buffer_get_text(bufferHandle);
2126 	} else {
2127 		stringPtr = GTK3.gtk_entry_get_text(textEntryHandle);
2128 	}
2129 
2130 	start = (int) OS.g_utf16_offset_to_utf8_offset(stringPtr, start);
2131 	end = (int) OS.g_utf16_offset_to_utf8_offset(stringPtr, end);
2132 
2133 	GTK.gtk_editable_set_position(textEntryHandle, start);
2134 	GTK.gtk_editable_select_region(textEntryHandle, start, end);
2135 }
2136 
setTextField(FieldPosition field, int value)2137 void setTextField(FieldPosition field, int value) {
2138 	int validValue = validateValueBounds(field, value);
2139 	setFieldOfInternalDataStructure(field, validValue);
2140 	setFieldOfInternalDataStructure(field, value);
2141 	updateControl();
2142 	if (currentField != null) {
2143 		selectField(currentField);
2144 	}
2145 }
2146 
validateValueBounds(FieldPosition field, int value)2147 private int validateValueBounds(FieldPosition field, int value) {
2148 	int calendarField = getCalendarField(field);
2149 	int max = calendar.getActualMaximum (calendarField);
2150 	int min = calendar.getActualMinimum (calendarField);
2151 	if (calendarField == Calendar.YEAR) {
2152 		max = MAX_YEAR;
2153 		min = MIN_YEAR;
2154 		/* Special case: convert 1 or 2-digit years into reasonable 4-digit years. */
2155 		int currentYear = Calendar.getInstance ().get (Calendar.YEAR);
2156 		int currentCentury = (currentYear / 100) * 100;
2157 		if (value < (currentYear + 30) % 100) value += currentCentury;
2158 		else if (value < 100) value += currentCentury - 100;
2159 	}
2160 	if (value > max) value = min; // wrap
2161 	if (value < min) value = max; // wrap
2162 	return value;
2163 }
2164 
2165 @Override
gtk_button_release_event(long widget, long event)2166 long gtk_button_release_event (long widget, long event) {
2167 	if (isDate() || isTime()) {
2168 		int [] eventButton = new int [1];
2169 		GDK.gdk_event_get_button(event, eventButton);
2170 
2171 		if (eventButton[0] == 1) { // left mouse button.
2172 			onTextMouseClick();
2173 		}
2174 	}
2175 	return super.gtk_button_release_event(widget, event);
2176 }
2177 
2178 @Override
gtk_gesture_release_event(long gesture, int n_press, double x, double y, long event)2179 void gtk_gesture_release_event(long gesture, int n_press, double x, double y, long event) {
2180 	if (isDate() || isTime()) {
2181 		int button = GTK.gtk_gesture_single_get_current_button(gesture);
2182 
2183 		if (button == 1) {
2184 			onTextMouseClick();
2185 		}
2186 	}
2187 
2188 	super.gtk_gesture_release_event(gesture, n_press, x, y, event);
2189 }
2190 
2191 /**
2192  * Output signal is called when Spinner's arrow buttons are triggered,
2193  * usually by clicking the mouse on the [gtk2: up/down] [gtk3: +/-] buttons.
2194  * On every click output is called twice presenting current and previous value.
2195  * This method compares two values and determines if Up or down arrow was called.
2196  */
2197 @Override
gtk_output(long widget)2198 long gtk_output (long widget) {
2199 	if (calendar == null) {
2200 		return 0; //Guard: Object not fully initialized yet.
2201 	}
2202 	int arrowType = getArrow(widget);
2203 	switch (arrowType) {
2204 	case SWT.ARROW_UP: // Gtk3 "+" button.
2205 		commitData();
2206 		incrementField(+1);
2207 		break;
2208 	case SWT.ARROW_DOWN: // Gtk3 "-" button.
2209 		commitData();
2210 		incrementField(-1);
2211 		break;
2212 	}
2213 	return 1;
2214 }
2215 
replaceCurrentlySelectedTextRegion(String string)2216 void replaceCurrentlySelectedTextRegion (String string) {
2217 	checkWidget ();
2218 	if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
2219 	byte [] buffer = Converter.wcsToMbcs (string, false);
2220 	int [] start = new int [1], end = new int [1];
2221 	GTK.gtk_editable_get_selection_bounds (textEntryHandle, start, end);
2222 	GTK.gtk_editable_delete_selection (textEntryHandle);
2223 	GTK.gtk_editable_insert_text (textEntryHandle, buffer, buffer.length, start);
2224 	GTK.gtk_editable_set_position (textEntryHandle, start [0]);
2225 }
2226 
onTextMouseClick()2227 void onTextMouseClick() {
2228 	if (calendar == null) {
2229 		return; // Guard: Object not fully initialized yet.
2230 	}
2231 	int clickPosition = getTextSelection().x;
2232 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
2233 	iterator.first();
2234 	int pos = 0;
2235 	do {
2236 		FieldPosition position = getFieldPosition(iterator);
2237 		iterator.setIndex(iterator.getRunLimit());
2238 		if (isSameField(position, currentField)) {
2239 			// use the current field instead then!
2240 			position = currentField;
2241 		}
2242 		int fieldWidth = position.getEndIndex() - position.getBeginIndex();
2243 		pos += fieldWidth;
2244 		if (position.getFieldAttribute() == null) {
2245 			continue;
2246 		}
2247 		if (pos >= clickPosition) {
2248 			FieldPosition selectField = new FieldPosition(position.getFieldAttribute());
2249 			selectField.setBeginIndex(pos - fieldWidth);
2250 			selectField.setEndIndex(pos);
2251 			selectField(selectField);
2252 			break;
2253 		}
2254 	} while (iterator.current() != CharacterIterator.DONE);
2255 
2256 }
2257 
getText(int start, int end)2258 String getText (int start, int end) {
2259 	checkWidget ();
2260 	if (!(start <= end && 0 <= end)) return "";
2261 	String str = getText ();
2262 	int length = str.length ();
2263 	end = Math.min (end, length - 1);
2264 	if (start > end) return "";
2265 	start = Math.max (0, start);
2266 	/*
2267 	* NOTE: The current implementation uses substring ()
2268 	* which can reference a potentially large character
2269 	* array.
2270 	*/
2271 	return str.substring (start, end + 1);
2272 }
2273 
selectAll()2274 void selectAll () {
2275 	checkWidget ();
2276 	if (textEntryHandle != 0)
2277 		GTK.gtk_editable_select_region (textEntryHandle, 0, -1);
2278 }
2279 
2280 
hideDateTime()2281 void hideDateTime () {
2282 	if (isDate () || isTime ()){
2283 		GTK.gtk_widget_hide (fixedHandle);
2284 	}
2285 }
2286 
2287 @Override
releaseWidget()2288 void releaseWidget () {
2289 	super.releaseWidget ();
2290 	if (fixedHandle != 0)
2291 		hideDateTime ();
2292 }
2293 
2294 /**
2295  * Returns a field with updated positionla data
2296  *
2297  * @param field
2298  * @return
2299  */
updateField(FieldPosition field)2300 private FieldPosition updateField(FieldPosition field) {
2301 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
2302 	while (iterator.current() != CharacterIterator.DONE) {
2303 		FieldPosition current = getFieldPosition(iterator);
2304 		iterator.setIndex(iterator.getRunLimit());
2305 		if (field == null || isSameField(current, field)) {
2306 			return current;
2307 		}
2308 	}
2309 	return field;
2310 }
2311 
2312 /**
2313  * Given a {@link FieldPosition} searches the next field in the format string
2314  *
2315  * @param field
2316  *            the Field to start from
2317  * @return the next {@link FieldPosition}
2318  */
getNextField(FieldPosition field)2319 private FieldPosition getNextField(FieldPosition field) {
2320 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
2321 	FieldPosition first = null;
2322 	boolean found = false;
2323 	while (iterator.current() != CharacterIterator.DONE) {
2324 		FieldPosition current = getFieldPosition(iterator);
2325 		iterator.setIndex(iterator.getRunLimit());
2326 		if (current.getFieldAttribute() == null) {
2327 			continue;
2328 		}
2329 		if (found) {
2330 			return current;
2331 		}
2332 		if (first == null) {
2333 			first = current;
2334 		}
2335 		if (isSameField(current, field)) {
2336 			found = true;
2337 		}
2338 	}
2339 	return first;
2340 }
2341 
2342 /**
2343  *
2344  * @param field
2345  * @return the next field of the given one
2346  */
getPreviousField(FieldPosition field)2347 private FieldPosition getPreviousField(FieldPosition field) {
2348 	AttributedCharacterIterator iterator = dateFormat.formatToCharacterIterator(calendar.getTime());
2349 	FieldPosition last = null;
2350 	do {
2351 		FieldPosition current = getFieldPosition(iterator);
2352 		if (isSameField(current, field)) {
2353 			if (last != null) {
2354 				return last;
2355 			}
2356 		}
2357 		if (current.getFieldAttribute() != null) {
2358 			last = current;
2359 		}
2360 		iterator.setIndex(iterator.getRunLimit());
2361 	} while (iterator.current() != CharacterIterator.DONE);
2362 	return last;
2363 }
2364 
2365 /**
2366  * Searches the current postion of the iterator for a {@link Field} and
2367  * constructs a {@link FieldPosition} from it
2368  *
2369  * @param iterator
2370  *            the iterator to use
2371  * @return a new {@link FieldPosition}
2372  */
getFieldPosition(AttributedCharacterIterator iterator)2373 private static FieldPosition getFieldPosition(AttributedCharacterIterator iterator) {
2374 	Set<Attribute> keySet = iterator.getAttributes().keySet();
2375 	for (Attribute attribute : keySet) {
2376 		if (attribute instanceof Field) {
2377 			return getFieldPosition((Field) attribute, iterator);
2378 		}
2379 	}
2380 	return getFieldPosition((Field) null, iterator);
2381 }
2382 
2383 /**
2384  * creates a {@link FieldPosition} out of a {@link Field} and and a
2385  * {@link AttributedCharacterIterator}s current position
2386  *
2387  * @param field
2388  *            the field to use
2389  * @param iterator
2390  *            the iterator to extract the data from
2391  * @return a {@link FieldPosition} init to this Field and begin/end index
2392  */
getFieldPosition(Field field, AttributedCharacterIterator iterator)2393 private static FieldPosition getFieldPosition(Field field, AttributedCharacterIterator iterator) {
2394 	FieldPosition position = new FieldPosition(field);
2395 	position.setBeginIndex(iterator.getRunStart());
2396 	position.setEndIndex(iterator.getRunLimit());
2397 	return position;
2398 }
2399 
2400 /**
2401  * Check if the given {@link FieldPosition} are considdered "the same", this is
2402  * when both are not <code>null</code> and reference the same
2403  * {@link java.text.Format.Field} attribute, or both of them have no
2404  * fieldattribute and have the same position
2405  *
2406  * @param p1
2407  *            first position to compare
2408  * @param p2
2409  *            second position to compare
2410  * @return <code>true</code> if considered the same, <code>false</code>
2411  *         otherwise
2412  */
isSameField(FieldPosition p1, FieldPosition p2)2413 private static boolean isSameField(FieldPosition p1, FieldPosition p2) {
2414 	if (p1 == p2) {
2415 		return true;
2416 	}
2417 	if (p1 == null || p2 == null) {
2418 		return false;
2419 	}
2420 	if (p1.getFieldAttribute() == null && p2.getFieldAttribute() == null) {
2421 		return p1.equals(p2);
2422 	}
2423 	if (p1.getFieldAttribute() == null) {
2424 		return false;
2425 	}
2426 	return p1.getFieldAttribute().equals(p2.getFieldAttribute());
2427 }
2428 
2429 /**
2430  * Extracts the calendarfield for the given fieldposition
2431  *
2432  * @param fieldPosition
2433  * @return the {@link Calendar} field or -1 if this is not a valid Fieldposition
2434  */
getCalendarField(FieldPosition fieldPosition)2435 private static int getCalendarField(FieldPosition fieldPosition) {
2436 	if ((fieldPosition.getFieldAttribute() instanceof Field)) {
2437 		return getCalendarField((Field) fieldPosition.getFieldAttribute());
2438 	} else {
2439 		return -1;
2440 	}
2441 }
2442 
2443 /**
2444  * Extracts the calendarfield transforming HOUR1 types to HOUR0
2445  *
2446  * @param field
2447  * @return the calendarfield coresponding to the {@link Field}
2448  */
getCalendarField(Field field)2449 private static int getCalendarField(Field field) {
2450 	if (Field.HOUR1.equals(field)) {
2451 		field = Field.HOUR0;
2452 	} else if (Field.HOUR_OF_DAY1.equals(field)) {
2453 		field = Field.HOUR_OF_DAY0;
2454 	}
2455 	return field.getCalendarField();
2456 }
2457 
2458 }