[NUI] Fix Picker's HeightSpecification
[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                 is24HourView = value;
241                 if (value == true)
242                 {
243                     Remove(ampmPicker);
244                     hourPicker.MinValue = 0;
245                     hourPicker.MaxValue = 23;
246                     hourPicker.CurrentValue = currentTime.Hour;
247                 }
248                 else
249                 {
250                     hourPicker.MinValue = 1;
251                     hourPicker.MaxValue = 12;
252                     PickersOrderSet(true);
253                     SetAmpmText();
254                     if (currentTime.Hour > 12)
255                     {
256                         ampmPicker.CurrentValue = 2;
257                         hourPicker.CurrentValue = currentTime.Hour - 12;
258                     }
259                 }
260             }
261         }
262
263
264         /// <inheritdoc/>
265         [EditorBrowsable(EditorBrowsableState.Never)]
266         protected override void OnEnabled(bool enabled)
267         {
268             base.OnEnabled(enabled);
269
270             hourPicker.IsEnabled = enabled;
271             minutePicker.IsEnabled = enabled;
272             ampmPicker.IsEnabled = enabled;
273         }
274
275         /// <summary>
276         /// Initialize TimePicker object.
277         /// </summary>
278         [EditorBrowsable(EditorBrowsableState.Never)]
279         public override void OnInitialize()
280         {
281             base.OnInitialize();
282             AccessibilityRole = Role.DateEditor;
283
284             hourPicker = new Picker()
285             {
286                 MinValue = 1,
287                 MaxValue = 12,
288                 Focusable = true,
289             };
290             hourPicker.ValueChanged += OnHourValueChanged;
291
292             minutePicker = new Picker()
293             {
294                 MinValue = 0,
295                 MaxValue = 59,
296                 Focusable = true,
297             };
298             minutePicker.ValueChanged += OnMinuteValueChanged;
299
300             ampmPicker = new Picker()
301             {
302                 MinValue = 1,
303                 MaxValue = 2,
304                 Focusable = true,
305             };
306             ampmPicker.ValueChanged += OnAmpmValueChanged;
307
308             currentTime = DateTime.Now;
309             if (currentTime.Hour > 12)
310             {
311                 ampmPicker.CurrentValue = 2;
312                 hourPicker.CurrentValue = currentTime.Hour - 12;
313             }
314             else
315             {
316                 ampmPicker.CurrentValue = 1;
317                 hourPicker.CurrentValue = currentTime.Hour;
318             }
319
320             minutePicker.CurrentValue = currentTime.Minute;
321
322             Initialize();
323         }
324
325         /// <summary>
326         /// Applies style to TimePicker.
327         /// </summary>
328         /// <param name="viewStyle">The style to apply.</param>
329         [EditorBrowsable(EditorBrowsableState.Never)]
330         public override void ApplyStyle(ViewStyle viewStyle)
331         {
332             base.ApplyStyle(viewStyle);
333
334             var timePickerStyle = viewStyle as TimePickerStyle;
335
336             if (timePickerStyle == null) return;
337
338             //Apply CellPadding.
339             if (timePickerStyle?.CellPadding != null && Layout != null)
340                 ((LinearLayout)Layout).CellPadding = new Size2D(timePickerStyle.CellPadding.Width, timePickerStyle.CellPadding.Height);
341
342             //Apply Internal Pickers style.
343             if (timePickerStyle?.Pickers != null && hourPicker != null && minutePicker != null && ampmPicker != null)
344             {
345                 hourPicker.ApplyStyle(timePickerStyle.Pickers);
346                 minutePicker.ApplyStyle(timePickerStyle.Pickers);
347                 ampmPicker.ApplyStyle(timePickerStyle.Pickers);
348             }
349         }
350
351         /// <summary>
352         /// ToDo : only key navigation is enabled, and value editing is added as an very simple operation. by toggling enter key, it switches edit mode.
353         /// ToDo : this should be fixed and changed properly by owner. (And UX SPEC should be referenced also)
354         /// </summary>
355         /// <param name="currentFocusedView"></param>
356         /// <param name="direction"></param>
357         /// <param name="loopEnabled"></param>
358         /// <returns></returns>
359         [EditorBrowsable(EditorBrowsableState.Never)]
360         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
361         {
362             if (currentFocusedView == hourPicker)
363             {
364                 if (direction == View.FocusDirection.Right)
365                 {
366                     return minutePicker;
367                 }
368
369             }
370             else if (currentFocusedView == minutePicker)
371             {
372                 if (direction == View.FocusDirection.Right)
373                 {
374                     return ampmPicker;
375                 }
376                 else if (direction == View.FocusDirection.Left)
377                 {
378                     return hourPicker;
379                 }
380             }
381             else if (currentFocusedView == ampmPicker)
382             {
383                 if (direction == View.FocusDirection.Left)
384                 {
385                     return minutePicker;
386                 }
387             }
388             return null;
389         }
390
391         [SuppressMessage("Microsoft.Reliability",
392                          "CA2000:DisposeObjectsBeforeLosingScope",
393                          Justification = "The CellPadding will be dispose when the time picker disposed")]
394         private void Initialize()
395         {
396             Layout = new LinearLayout()
397             {
398                 LinearOrientation = LinearLayout.Orientation.Horizontal,
399             };
400
401             is24HourView = false;
402
403             PickersOrderSet(false);
404             SetAmpmText();
405         }
406
407         private void ChangeTime(int hour, int minute, bool hourUpdate)
408         {
409             if (hourUpdate)
410                 currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, currentTime.Minute, 0);
411             else
412                 currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, minute, 0);
413         }
414
415         private void OnHourValueChanged(object sender, ValueChangedEventArgs e)
416         {
417             if (currentTime.Hour == e.Value) return;
418
419             if (!is24HourView)
420             {
421                 if (isAm)
422                 {
423                     if (e.Value == 12) ChangeTime(0, 0, true);
424                     else ChangeTime(e.Value, 0, true);
425                 }
426                 else
427                 {
428                     if (e.Value == 12) ChangeTime(12, 0, true);
429                     else ChangeTime(e.Value + 12, 0, true);
430                 }
431             }
432             else
433                 ChangeTime(e.Value, 0, true);
434
435             OnTimeChanged();
436         }
437
438         private void OnMinuteValueChanged(object sender, ValueChangedEventArgs e)
439         {
440             if (currentTime.Minute == e.Value) return;
441
442             ChangeTime(0, e.Value, false);
443
444             OnTimeChanged();
445         }
446
447         private void OnAmpmValueChanged(object sender, ValueChangedEventArgs e)
448         {
449             if ((isAm && e.Value == 1) || (!isAm && e.Value == 2)) return;
450
451             if (e.Value == 1)
452             { //AM
453                 if (currentTime.Hour == 12) ChangeTime(0, 0, true);
454                 else ChangeTime(currentTime.Hour - 12, 0, true);
455
456                 isAm = true;
457             }
458             else
459             { //PM
460                 if (currentTime.Hour == 0) ChangeTime(12, 0, true);
461                 else ChangeTime(currentTime.Hour + 12, 0, true);
462
463                 isAm = false;
464             }
465
466             OnTimeChanged();
467         }
468
469         private void OnTimeChanged()
470         {
471             TimeChangedEventArgs eventArgs = new TimeChangedEventArgs(currentTime);
472             TimeChanged?.Invoke(this, eventArgs);
473         }
474
475         private void PickersOrderSet(bool ampmForceSet)
476         {
477             //FIXME: Check the pickers located in already proper position or not.
478             Remove(hourPicker);
479             Remove(minutePicker);
480             Remove(ampmPicker);
481
482             //Get current system locale's time pattern
483             DateTimeFormatInfo timeFormatInfo = CultureInfo.CurrentCulture.DateTimeFormat;
484             String timePattern = timeFormatInfo.ShortTimePattern;
485             String[] timePatternArray = timePattern.Split(' ', ':');
486
487             foreach (String format in timePatternArray)
488             {
489                 if (format.IndexOf("H") != -1 || format.IndexOf("h") != -1) Add(hourPicker);
490                 else if (format.IndexOf("M") != -1 || format.IndexOf("m") != -1) Add(minutePicker);
491                 else if (format.IndexOf("t") != -1)
492                 {
493                     is24HourView = false;
494                     ampmForceSet = false;
495                     Add(ampmPicker);
496                 }
497             }
498
499             if (ampmForceSet) Add(ampmPicker);
500         }
501
502         private void SetAmpmText()
503         {
504             //FIXME: There is no localeChanged Event for Component now
505             //       AMPM text has to update when system locale changed.
506             CultureInfo info = CultureInfo.CurrentCulture;
507             ampmText = new string[] { info.DateTimeFormat.AMDesignator, info.DateTimeFormat.PMDesignator };
508             ampmPicker.DisplayedValues = new ReadOnlyCollection<string>(ampmText);
509         }
510     }
511 }