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 }