83794ac65cdcc78590cd32cca085ff63629a2a8e
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / TimePicker.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.Collections.ObjectModel;
20 using System.ComponentModel;
21 using System.Diagnostics.CodeAnalysis;
22 using System.Globalization;
23 using Tizen.NUI.Binding;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// TimeChangedEventArgs is a class to notify changed TimePicker value argument which will sent to user.
29     /// </summary>
30     /// <since_tizen> 9 </since_tizen>
31     public class TimeChangedEventArgs : EventArgs
32     {
33         /// <summary>
34         /// TimeChangedEventArgs default constructor.
35         /// <param name="time">time value of TimePicker.</param>
36         /// </summary>
37         [EditorBrowsable(EditorBrowsableState.Never)]
38         public TimeChangedEventArgs(DateTime time)
39         {
40             Time = time;
41         }
42
43         /// <summary>
44         /// TimeChangedEventArgs default constructor.
45         /// <returns>The current time value of TimePicker.</returns>
46         /// </summary>
47         /// <since_tizen> 9 </since_tizen>
48         public DateTime Time { get; }
49     }
50
51     /// <summary>
52     /// TimePicker is a class which provides a function that allows the user to select 
53     /// a time through a scrolling motion by expressing the specified value as a list.
54     /// TimePicker expresses the current time using the locale information of the system.
55     /// </summary>
56     /// <since_tizen> 9 </since_tizen>
57     public class TimePicker : Control
58     {
59         /// <summary>
60         /// TimeProperty
61         /// </summary>
62         [EditorBrowsable(EditorBrowsableState.Never)]
63         public static readonly BindableProperty TimeProperty = BindableProperty.Create(nameof(Time), typeof(DateTime), typeof(TimePicker), default(DateTime), propertyChanged: (bindable, oldValue, newValue) =>
64         {
65             var instance = (TimePicker)bindable;
66             if (newValue != null)
67             {
68                 instance.InternalTime = (DateTime)newValue;
69             }
70         },
71         defaultValueCreator: (bindable) =>
72         {
73             var instance = (TimePicker)bindable;
74             return instance.InternalTime;
75         });
76
77         /// <summary>
78         /// Is24HourViewProperty
79         /// </summary>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         public static readonly BindableProperty Is24HourViewProperty = BindableProperty.Create(nameof(Is24HourView), typeof(bool), typeof(TimePicker), default(bool), propertyChanged: (bindable, oldValue, newValue) =>
82         {
83             var instance = (TimePicker)bindable;
84             if (newValue != null)
85             {
86                 instance.InternalIs24HourView = (bool)newValue;
87             }
88         },
89         defaultValueCreator: (bindable) =>
90         {
91             var instance = (TimePicker)bindable;
92             return instance.InternalIs24HourView;
93         });
94
95         private bool isAm;
96         private bool is24HourView;
97         private DateTime currentTime;
98         private String[] ampmText;
99         private Picker hourPicker;
100         private Picker minutePicker;
101         private Picker ampmPicker;
102
103         /// <summary>
104         /// Creates a new instance of TimePicker.
105         /// </summary>
106         /// <since_tizen> 9 </since_tizen>
107         public TimePicker()
108         {
109             SetKeyboardNavigationSupport(true);
110         }
111
112         /// <summary>
113         /// Creates a new instance of TimePicker.
114         /// </summary>
115         /// <param name="style">Creates TimePicker by special style defined in UX.</param>
116         /// <since_tizen> 9 </since_tizen>
117         public TimePicker(string style) : base(style)
118         {
119             SetKeyboardNavigationSupport(true);
120         }
121
122         /// <summary>
123         /// Creates a new instance of TimePicker.
124         /// </summary>
125         /// <param name="timePickerStyle">Creates TimePicker by style customized by user.</param>
126         /// <since_tizen> 9 </since_tizen>
127         public TimePicker(TimePickerStyle timePickerStyle) : base(timePickerStyle)
128         {
129             SetKeyboardNavigationSupport(true);
130         }
131
132         /// <summary>
133         /// Dispose TimePicker and all children on it.
134         /// </summary>
135         /// <param name="type">Dispose type.</param>
136         [EditorBrowsable(EditorBrowsableState.Never)]
137         protected override void Dispose(DisposeTypes type)
138         {
139             if (disposed)
140             {
141                 return;
142             }
143
144             if (type == DisposeTypes.Explicit)
145             {
146                 Remove(hourPicker);
147                 Utility.Dispose(hourPicker);
148                 hourPicker = null;
149                 Remove(minutePicker);
150                 Utility.Dispose(minutePicker);
151                 minutePicker = null;
152                 Remove(ampmPicker);
153                 Utility.Dispose(ampmPicker);
154                 ampmPicker = null;
155             }
156
157             base.Dispose(type);
158         }
159
160         /// <summary>
161         /// An event emitted when TimePicker value changed, user can subscribe or unsubscribe to this event handler.
162         /// </summary>
163         /// <since_tizen> 9 </since_tizen>
164         public event EventHandler<TimeChangedEventArgs> TimeChanged;
165
166         /// <summary>
167         /// The hour value of TimePicker.
168         /// </summary>
169         /// <since_tizen> 9 </since_tizen>
170         public DateTime Time
171         {
172             get
173             {
174                 return (DateTime)GetValue(TimeProperty);
175             }
176             set
177             {
178                 SetValue(TimeProperty, value);
179                 NotifyPropertyChanged();
180             }
181         }
182         private DateTime InternalTime
183         {
184             get
185             {
186                 return currentTime;
187             }
188             set
189             {
190                 currentTime = value;
191                 if (!is24HourView)
192                 {
193                     if (currentTime.Hour >= 12 && currentTime.Hour <= 23)
194                     {
195                         isAm = false;
196                         if (currentTime.Hour == 12) hourPicker.CurrentValue = currentTime.Hour;
197                         else hourPicker.CurrentValue = currentTime.Hour - 12;
198                         ampmPicker.CurrentValue = 2;
199                     }
200                     else
201                     {
202                         isAm = true;
203                         if (currentTime.Hour == 0) hourPicker.CurrentValue = 12;
204                         else hourPicker.CurrentValue = currentTime.Hour;
205                         ampmPicker.CurrentValue = 1;
206                     }
207                 }
208                 else hourPicker.CurrentValue = currentTime.Hour;
209
210                 minutePicker.CurrentValue = currentTime.Minute;
211             }
212         }
213
214         /// <summary>
215         /// The is24hourview value of TimePicker.
216         /// </summary>
217         /// <since_tizen> 9 </since_tizen>
218         public bool Is24HourView
219         {
220             get
221             {
222                 return (bool)GetValue(Is24HourViewProperty);
223             }
224             set
225             {
226                 SetValue(Is24HourViewProperty, value);
227                 NotifyPropertyChanged();
228             }
229         }
230         private bool InternalIs24HourView
231         {
232             get
233             {
234                 return is24HourView;
235             }
236             set
237             {
238                 if (is24HourView == value) return;
239
240                 Console.WriteLine(" Is 24 Hour View");
241                 is24HourView = value;
242                 if (value == true)
243                 {
244                     Remove(ampmPicker);
245                     hourPicker.MinValue = 0;
246                     hourPicker.MaxValue = 23;
247                     hourPicker.CurrentValue = currentTime.Hour;
248                 }
249                 else
250                 {
251                     hourPicker.MinValue = 1;
252                     hourPicker.MaxValue = 12;
253                     PickersOrderSet(true);
254                     SetAmpmText();
255                     if (currentTime.Hour > 12)
256                     {
257                         ampmPicker.CurrentValue = 2;
258                         hourPicker.CurrentValue = currentTime.Hour - 12;
259                     }
260                 }
261             }
262         }
263
264         /// <summary>
265         /// Initialize TimePicker object.
266         /// </summary>
267         [EditorBrowsable(EditorBrowsableState.Never)]
268         public override void OnInitialize()
269         {
270             base.OnInitialize();
271             SetAccessibilityConstructor(Role.DateEditor);
272
273             hourPicker = new Picker()
274             {
275                 MinValue = 1,
276                 MaxValue = 12,
277                 Focusable = true,
278             };
279             hourPicker.ValueChanged += OnHourValueChanged;
280
281             minutePicker = new Picker()
282             {
283                 MinValue = 0,
284                 MaxValue = 59,
285                 Focusable = true,
286             };
287             minutePicker.ValueChanged += OnMinuteValueChanged;
288
289             ampmPicker = new Picker()
290             {
291                 MinValue = 1,
292                 MaxValue = 2,
293                 Focusable = true,
294             };
295             ampmPicker.ValueChanged += OnAmpmValueChanged;
296
297             currentTime = DateTime.Now;
298             if (currentTime.Hour > 12)
299             {
300                 ampmPicker.CurrentValue = 2;
301                 hourPicker.CurrentValue = currentTime.Hour - 12;
302             }
303             else
304             {
305                 ampmPicker.CurrentValue = 1;
306                 hourPicker.CurrentValue = currentTime.Hour;
307             }
308
309             minutePicker.CurrentValue = currentTime.Minute;
310
311             Initialize();
312         }
313
314         /// <summary>
315         /// Applies style to TimePicker.
316         /// </summary>
317         /// <param name="viewStyle">The style to apply.</param>
318         [EditorBrowsable(EditorBrowsableState.Never)]
319         public override void ApplyStyle(ViewStyle viewStyle)
320         {
321             base.ApplyStyle(viewStyle);
322
323             var timePickerStyle = viewStyle as TimePickerStyle;
324
325             if (timePickerStyle == null) return;
326
327             //Apply CellPadding.
328             if (timePickerStyle?.CellPadding != null && Layout != null)
329                 ((LinearLayout)Layout).CellPadding = new Size2D(timePickerStyle.CellPadding.Width, timePickerStyle.CellPadding.Height);
330
331             //Apply Internal Pickers style.
332             if (timePickerStyle?.Pickers != null && hourPicker != null && minutePicker != null && ampmPicker != null)
333             {
334                 hourPicker.ApplyStyle(timePickerStyle.Pickers);
335                 minutePicker.ApplyStyle(timePickerStyle.Pickers);
336                 ampmPicker.ApplyStyle(timePickerStyle.Pickers);
337             }
338         }
339
340         /// <summary>
341         /// ToDo : only key navigation is enabled, and value editing is added as an very simple operation. by toggling enter key, it switches edit mode. 
342         /// ToDo : this should be fixed and changed properly by owner. (And UX SPEC should be referenced also)
343         /// </summary>
344         /// <param name="currentFocusedView"></param>
345         /// <param name="direction"></param>
346         /// <param name="loopEnabled"></param>
347         /// <returns></returns>
348         [EditorBrowsable(EditorBrowsableState.Never)]
349         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
350         {
351             if (currentFocusedView == hourPicker)
352             {
353                 if (direction == View.FocusDirection.Right)
354                 {
355                     return minutePicker;
356                 }
357
358             }
359             else if (currentFocusedView == minutePicker)
360             {
361                 if (direction == View.FocusDirection.Right)
362                 {
363                     return ampmPicker;
364                 }
365                 else if (direction == View.FocusDirection.Left)
366                 {
367                     return hourPicker;
368                 }
369             }
370             else if (currentFocusedView == ampmPicker)
371             {
372                 if (direction == View.FocusDirection.Left)
373                 {
374                     return minutePicker;
375                 }
376             }
377             return null;
378         }
379
380         [SuppressMessage("Microsoft.Reliability",
381                          "CA2000:DisposeObjectsBeforeLosingScope",
382                          Justification = "The CellPadding will be dispose when the time picker disposed")]
383         private void Initialize()
384         {
385             HeightSpecification = LayoutParamPolicies.MatchParent;
386
387             Layout = new LinearLayout()
388             {
389                 LinearOrientation = LinearLayout.Orientation.Horizontal,
390             };
391             Console.WriteLine("initialize");
392
393             is24HourView = false;
394
395             PickersOrderSet(false);
396             SetAmpmText();
397         }
398
399         private void ChangeTime(int hour, int minute, bool hourUpdate)
400         {
401             if (hourUpdate)
402                 currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, currentTime.Minute, 0);
403             else
404                 currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, minute, 0);
405         }
406
407         private void OnHourValueChanged(object sender, ValueChangedEventArgs e)
408         {
409             if (currentTime.Hour == e.Value) return;
410
411             if (!is24HourView)
412             {
413                 if (isAm)
414                 {
415                     if (e.Value == 12) ChangeTime(0, 0, true);
416                     else ChangeTime(e.Value, 0, true);
417                 }
418                 else
419                 {
420                     if (e.Value == 12) ChangeTime(12, 0, true);
421                     else ChangeTime(e.Value + 12, 0, true);
422                 }
423             }
424             else
425                 ChangeTime(e.Value, 0, true);
426
427             OnTimeChanged();
428         }
429
430         private void OnMinuteValueChanged(object sender, ValueChangedEventArgs e)
431         {
432             if (currentTime.Minute == e.Value) return;
433
434             ChangeTime(0, e.Value, false);
435
436             OnTimeChanged();
437         }
438
439         private void OnAmpmValueChanged(object sender, ValueChangedEventArgs e)
440         {
441             if ((isAm && e.Value == 1) || (!isAm && e.Value == 2)) return;
442
443             if (e.Value == 1)
444             { //AM
445                 if (currentTime.Hour == 12) ChangeTime(0, 0, true);
446                 else ChangeTime(currentTime.Hour - 12, 0, true);
447
448                 isAm = true;
449             }
450             else
451             { //PM
452                 if (currentTime.Hour == 0) ChangeTime(12, 0, true);
453                 else ChangeTime(currentTime.Hour + 12, 0, true);
454
455                 isAm = false;
456             }
457
458             OnTimeChanged();
459         }
460
461         private void OnTimeChanged()
462         {
463             TimeChangedEventArgs eventArgs = new TimeChangedEventArgs(currentTime);
464             TimeChanged?.Invoke(this, eventArgs);
465         }
466
467         private void PickersOrderSet(bool ampmForceSet)
468         {
469             //FIXME: Check the pickers located in already proper position or not.
470             Remove(hourPicker);
471             Remove(minutePicker);
472             Remove(ampmPicker);
473
474             //Get current system locale's time pattern
475             DateTimeFormatInfo timeFormatInfo = CultureInfo.CurrentCulture.DateTimeFormat;
476             String timePattern = timeFormatInfo.ShortTimePattern;
477             String[] timePatternArray = timePattern.Split(' ', ':');
478
479             foreach (String format in timePatternArray)
480             {
481                 if (format.IndexOf("H") != -1 || format.IndexOf("h") != -1) Add(hourPicker);
482                 else if (format.IndexOf("M") != -1 || format.IndexOf("m") != -1) Add(minutePicker);
483                 else if (format.IndexOf("t") != -1)
484                 {
485                     is24HourView = false;
486                     ampmForceSet = false;
487                     Add(ampmPicker);
488                 }
489             }
490
491             if (ampmForceSet) Add(ampmPicker);
492         }
493
494         private void SetAmpmText()
495         {
496             //FIXME: There is no localeChanged Event for Component now
497             //       AMPM text has to update when system locale changed.
498             CultureInfo info = CultureInfo.CurrentCulture;
499             ampmText = new string[] { info.DateTimeFormat.AMDesignator, info.DateTimeFormat.PMDesignator };
500             ampmPicker.DisplayedValues = new ReadOnlyCollection<string>(ampmText);
501         }
502     }
503 }