[NUI][ATSPI] Not highlightable for AlertDialog
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / AlertDialog.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.ComponentModel;
20 using System.Collections.Generic;
21 using Tizen.NUI.BaseComponents;
22
23 namespace Tizen.NUI.Components
24 {
25     /// <summary>
26     /// AlertDialog class shows a dialog with title, message and action buttons.
27     /// </summary>
28     /// <since_tizen> 9 </since_tizen>
29     public class AlertDialog : Control
30     {
31         private string title = null;
32         private string message = null;
33
34         private View titleContent = null;
35         private View content = null;
36         private View actionContent = null;
37         private IEnumerable<View> actionContentViews = null;
38
39         private View defaultTitleContent = null;
40         private View defaultContent = null;
41         private View defaultActionContent = null;
42         // FIXME: Now AlertDialog.Padding Top and Bottom increases AlertDialog size incorrectly.
43         //        Until the bug is fixed, padding view is added after action content.
44         private View defaultActionContentPadding = null;
45
46         private AlertDialogStyle alertDialogStyle => ViewStyle as AlertDialogStyle;
47
48         private bool styleApplied = false;
49
50         /// <summary>
51         /// Creates a new instance of AlertDialog.
52         /// </summary>
53         /// <since_tizen> 9 </since_tizen>
54         public AlertDialog() : base()
55         {
56             Initialize();
57         }
58
59         /// <summary>
60         /// Creates a new instance of AlertDialog.
61         /// </summary>
62         /// <param name="style">Creates AlertDialog by special style defined in UX.</param>
63         /// <since_tizen> 9 </since_tizen>
64         public AlertDialog(string style) : base(style)
65         {
66             Initialize();
67         }
68
69         /// <summary>
70         /// Creates a new instance of AlertDialog.
71         /// </summary>
72         /// <param name="alertDialogStyle">Creates AlertDialog by style customized by user.</param>
73         /// <since_tizen> 9 </since_tizen>
74         public AlertDialog(AlertDialogStyle alertDialogStyle) : base(alertDialogStyle)
75         {
76             Initialize();
77         }
78
79         /// <inheritdoc/>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         protected override void Dispose(DisposeTypes type)
82         {
83             if (disposed)
84             {
85                 return;
86             }
87
88             if (type == DisposeTypes.Explicit)
89             {
90                 if (titleContent != null)
91                 {
92                     Utility.Dispose(titleContent);
93                 }
94
95                 if (content != null)
96                 {
97                     Utility.Dispose(content);
98                 }
99
100                 if (actionContent != null)
101                 {
102                     Utility.Dispose(actionContent);
103                 }
104
105                 // FIXME: Now AlertDialog.Padding Top and Bottom increases AlertDialog size incorrectly.
106                 //        Until the bug is fixed, padding view is added after action content.
107                 if (defaultActionContentPadding != null)
108                 {
109                     Utility.Dispose(defaultActionContentPadding);
110                 }
111             }
112
113             base.Dispose(type);
114         }
115
116         /// <summary>
117         /// Applies style to AlertDialog.
118         /// </summary>
119         /// <param name="viewStyle">The style to apply.</param>
120         /// <since_tizen> 9 </since_tizen>
121         public override void ApplyStyle(ViewStyle viewStyle)
122         {
123             styleApplied = false;
124
125             base.ApplyStyle(viewStyle);
126
127             // Apply Title style.
128             if ((alertDialogStyle?.TitleTextLabel != null) && (DefaultTitleContent is TextLabel))
129             {
130                 ((TextLabel)DefaultTitleContent)?.ApplyStyle(alertDialogStyle.TitleTextLabel);
131             }
132
133             // Apply Message style.
134             if ((alertDialogStyle?.MessageTextLabel != null) && (DefaultContent is TextLabel))
135             {
136                 ((TextLabel)DefaultContent)?.ApplyStyle(alertDialogStyle.MessageTextLabel);
137             }
138
139             // Apply ActionContent style.
140             if (alertDialogStyle?.ActionContent != null)
141             {
142                 DefaultActionContent?.ApplyStyle(alertDialogStyle.ActionContent);
143             }
144
145             styleApplied = true;
146
147             // Calculate dialog position and children's positions based on padding sizes.
148             CalculatePosition();
149         }
150
151         /// <summary>
152         /// Title text of AlertDialog.
153         /// Title text is set to TitleContent's Text if TitleContent is TextLabel.
154         /// If TitleContent's Text is set manually by user, then it is not guaranteed that TitleContent's Text is the same with Title text.
155         /// </summary>
156         /// <since_tizen> 9 </since_tizen>
157         public string Title
158         {
159             get
160             {
161                 return title;
162             }
163             set
164             {
165                 if (title == value)
166                 {
167                     return;
168                 }
169
170                 title = value;
171
172                 if (TitleContent is TextLabel textLabel)
173                 {
174                     textLabel.Text = title;
175                 }
176             }
177         }
178
179         /// <summary>
180         /// Title content of AlertDialog.
181         /// TitleContent is added as a child of AlertDialog automatically.
182         /// Title text is set to TitleContent's Text if TitleContent is TextLabel.
183         /// If TitleContent's Text is set manually by user, then it is not guaranteed that TitleContent's Text is the same with Title text.
184         /// </summary>
185         /// <since_tizen> 9 </since_tizen>
186         public View TitleContent
187         {
188             get
189             {
190                 return titleContent;
191             }
192             set
193             {
194                 if (titleContent == value)
195                 {
196                     return;
197                 }
198
199                 if (titleContent != null)
200                 {
201                     Remove(titleContent);
202                 }
203
204                 titleContent = value;
205                 if (titleContent == null)
206                 {
207                     return;
208                 }
209
210                 if (titleContent is TextLabel textLabel)
211                 {
212                     textLabel.Text = Title;
213                 }
214
215                 ResetContent();
216             }
217         }
218
219         /// <summary>
220         /// Message text of AlertDialog.
221         /// Message text is set to Content's Text if Content is TextLabel.
222         /// If Content's Text is set manually by user, then it is not guaranteed that Content's Text is the same with Message text.
223         /// </summary>
224         /// <since_tizen> 9 </since_tizen>
225         public string Message
226         {
227             get
228             {
229                 return message;
230             }
231             set
232             {
233                 if (message == value)
234                 {
235                     return;
236                 }
237
238                 message = value;
239
240                 if (Content is TextLabel textLabel)
241                 {
242                     textLabel.Text = message;
243                 }
244             }
245         }
246
247         /// <summary>
248         /// Content of AlertDialog.
249         /// Content is added as a child of AlertDialog automatically.
250         /// Message text is set to Content's Text if Content is TextLabel.
251         /// If Content's Text is set manually by user, then it is not guaranteed that Content's Text is the same with Message text.
252         /// </summary>
253         /// <since_tizen> 9 </since_tizen>
254         public View Content
255         {
256             get
257             {
258                 return content;
259             }
260             set
261             {
262                 if (content == value)
263                 {
264                     return;
265                 }
266
267                 if (content != null)
268                 {
269                     Remove(content);
270                 }
271
272                 content = value;
273                 if (content == null)
274                 {
275                     return;
276                 }
277
278                 if (content is TextLabel textLabel)
279                 {
280                     textLabel.Text = message;
281                 }
282
283                 ResetContent();
284             }
285         }
286
287         /// <summary>
288         /// Action views of AlertDialog.
289         /// Action views are added as children of ActionContent.
290         /// When Actions are set, previous Actions are removed from ActionContent.
291         /// </summary>
292         /// <since_tizen> 9 </since_tizen>
293         public IEnumerable<View> Actions
294         {
295             get
296             {
297                 return actionContentViews;
298             }
299             set
300             {
301                 if (ActionContent == null)
302                 {
303                     actionContentViews = value;
304                     return;
305                 }
306
307                 if (actionContentViews != null)
308                 {
309                     foreach (var oldAction in actionContentViews)
310                     {
311                         if (ActionContent.Children?.Contains(oldAction) == true)
312                         {
313                             ActionContent.Children.Remove(oldAction);
314                         }
315                     }
316                 }
317
318                 actionContentViews = value;
319
320                 if (actionContentViews == null)
321                 {
322                     return;
323                 }
324
325                 foreach (var action in actionContentViews)
326                 {
327                     ActionContent.Add(action);
328                 }
329             }
330         }
331
332         /// <summary>
333         /// Action content of AlertDialog.
334         /// ActionContent is added as a child of AlertDialog automatically.
335         /// Actions are added as children of ActionContent.
336         /// </summary>
337         /// <since_tizen> 9 </since_tizen>
338         public View ActionContent
339         {
340              get
341              {
342                 return actionContent;
343              }
344              set
345              {
346                 if (actionContent == value)
347                  {
348                      return;
349                  }
350
351                 var oldActionContent = actionContent;
352                 actionContent = value;
353
354                 // Add views first before remove previous action content
355                 // not to cause Garbage Collector collects views.
356                 if ((actionContent != null) && (Actions != null))
357                 {
358                     foreach (var action in Actions)
359                     {
360                         actionContent.Add(action);
361                     }
362                 }
363
364                 if (oldActionContent != null)
365                 {
366                     Remove(oldActionContent);
367                 }
368
369                 if (actionContent == null)
370                 {
371                     return;
372                 }
373
374                 ResetContent();
375             }
376         }
377
378         /// <summary>
379         /// AccessibilityGetName.
380         /// </summary>
381         [EditorBrowsable(EditorBrowsableState.Never)]
382         protected override string AccessibilityGetName()
383         {
384             if (!String.IsNullOrEmpty(Title))
385             {
386                 return Title;
387             }
388             else
389             {
390                 return Message;
391             }
392         }
393
394         /// <summary>
395         /// Initialize AT-SPI object.
396         /// </summary>
397         [EditorBrowsable(EditorBrowsableState.Never)]
398         public override void OnInitialize()
399         {
400             base.OnInitialize();
401             SetAccessibilityConstructor(Role.Dialog);
402             AppendAccessibilityAttribute("sub-role", "Alert");
403             Show(); // calls AddPopup()
404         }
405
406         /// <summary>
407         /// Informs AT-SPI bridge about the set of AT-SPI states associated with this object.
408         /// </summary>
409         [EditorBrowsable(EditorBrowsableState.Never)]
410         protected override AccessibilityStates AccessibilityCalculateStates()
411         {
412             var states = base.AccessibilityCalculateStates();
413             FlagSetter(ref states, AccessibilityStates.Modal, true);
414             return states;
415         }
416
417
418         /// <summary>
419         /// Default title content of AlertDialog.
420         /// If Title is set, then default title content is automatically displayed.
421         /// </summary>
422         [EditorBrowsable(EditorBrowsableState.Never)]
423         protected View DefaultTitleContent
424         {
425             get
426             {
427                 if (defaultTitleContent == null)
428                 {
429                     defaultTitleContent = CreateDefaultTitleContent();
430                 }
431
432                 return defaultTitleContent;
433             }
434         }
435
436         /// <summary>
437         /// Default content of AlertDialog.
438         /// If Message is set, then default content is automatically displayed.
439         /// </summary>
440         [EditorBrowsable(EditorBrowsableState.Never)]
441         protected View DefaultContent
442         {
443             get
444             {
445                 if (defaultContent == null)
446                 {
447                     defaultContent = CreateDefaultContent();
448                 }
449
450                 return defaultContent;
451             }
452         }
453
454         /// <summary>
455         /// Default action content of AlertDialog.
456         /// If Actions are set, then default action content is automatically displayed.
457         /// </summary>
458         [EditorBrowsable(EditorBrowsableState.Never)]
459         protected View DefaultActionContent
460         {
461             get
462             {
463                 if (defaultActionContent == null)
464                 {
465                     defaultActionContent = CreateDefaultActionContent();
466                 }
467
468                 // FIXME: Now AlertDialog.Padding Top and Bottom increases AlertDialog size incorrectly.
469                 //        Until the bug is fixed, padding view is added after action content.
470                 if (defaultActionContentPadding == null)
471                 {
472                     defaultActionContentPadding = CreateDefaultActionContentPadding();
473                 }
474
475                 return defaultActionContent;
476             }
477         }
478
479         private void Initialize()
480         {
481             Layout = new LinearLayout()
482             {
483                 LinearOrientation = LinearLayout.Orientation.Vertical,
484             };
485
486             this.Relayout += OnRelayout;
487
488             TitleContent = DefaultTitleContent;
489
490             Content = DefaultContent;
491
492             ActionContent = DefaultActionContent;
493         }
494
495         private void ResetContent()
496         {
497             //To keep the order of TitleContent, Content and ActionContent,
498             //the existing contents are removed and added again.
499             if (titleContent != null)
500             {
501                 Remove(titleContent);
502             }
503
504             if (content != null)
505             {
506                 Remove(content);
507             }
508
509             if (actionContent != null)
510             {
511                 Remove(actionContent);
512             }
513
514             if (titleContent != null)
515             {
516                 Add(titleContent);
517             }
518
519             if (content != null)
520             {
521                 Add(content);
522             }
523
524             if (actionContent != null)
525             {
526                 Add(actionContent);
527
528                 // FIXME: Now AlertDialog.Padding Top and Bottom increases AlertDialog size incorrectly.
529                 //        Until the bug is fixed, padding view is added after action content.
530                 if (actionContent == defaultActionContent)
531                 {
532                     if (defaultActionContentPadding != null)
533                     {
534                         Add(defaultActionContentPadding);
535                     }
536                 }
537             }
538         }
539
540         private TextLabel CreateDefaultTitleContent()
541         {
542             return new TextLabel();
543         }
544
545         private TextLabel CreateDefaultContent()
546         {
547             return new TextLabel();
548         }
549
550         private View CreateDefaultActionContent()
551         {
552             return new View()
553             {
554                 Layout = new LinearLayout()
555                 {
556                     LinearOrientation = LinearLayout.Orientation.Horizontal,
557                 },
558             };
559         }
560
561         // FIXME: Now AlertDialog.Padding Top and Bottom increases AlertDialog size incorrectly.
562         //        Until the bug is fixed, padding view is added after action content.
563         private View CreateDefaultActionContentPadding()
564         {
565             var layout = Layout as LinearLayout;
566
567             if ((layout == null) || (defaultActionContent == null))
568             {
569                 return null;
570             }
571
572             View paddingView = null;
573
574             using (Size2D size = new Size2D(defaultActionContent.Size2D.Width, defaultActionContent.Size2D.Height))
575             {
576                 if (layout.LinearOrientation == LinearLayout.Orientation.Horizontal)
577                 {
578                     size.Width = 40;
579                 }
580                 else
581                 {
582                     size.Height = 40;
583                 }
584
585                 paddingView = new View()
586                 {
587                     Size2D = new Size2D(size.Width, size.Height),
588                 };
589             }
590
591             return paddingView;
592         }
593
594         private void OnRelayout(object sender, EventArgs e)
595         {
596             // Calculate dialog position and children's positions based on padding sizes.
597             CalculatePosition();
598         }
599
600         // Calculate dialog position and children's positions based on padding sizes.
601         private void CalculatePosition()
602         {
603             if (styleApplied == false)
604             {
605                 return;
606             }
607
608             CalculateActionsCellPadding();
609
610             var size = Size2D;
611             var parent = GetParent();
612             Size2D parentSize;
613
614             if ((parent != null) && (parent is View))
615             {
616                 parentSize = ((View)parent).Size;
617             }
618             else
619             {
620                 parentSize = NUIApplication.GetDefaultWindow().Size;
621             }
622
623             Position2D = new Position2D((parentSize.Width - size.Width) / 2, (parentSize.Height - size.Height) / 2);
624         }
625
626         // Calculate CellPadding among Actions if ActionContent is LinearLayout.
627         private void CalculateActionsCellPadding()
628         {
629             if ((ActionContent != DefaultActionContent) || (ActionContent.Layout is LinearLayout == false))
630             {
631                 return;
632             }
633
634             if (Actions == null)
635             {
636                 return;
637             }
638
639             var size = Size2D;
640             var layout = ActionContent.Layout as LinearLayout;
641             int count = 0;
642
643             if (layout.LinearOrientation == LinearLayout.Orientation.Horizontal)
644             {
645                 int actionsWidth = 0;
646
647                 foreach (var action in Actions)
648                 {
649                     actionsWidth += ((View)action).Size2D.Width + ((((View)action).Margin?.Start + ((View)action).Margin?.End) ?? 0);
650                     count++;
651                 }
652
653                 if (count > 1)
654                 {
655                     actionsWidth += (Padding?.Start + Padding?.End) ?? 0;
656                     var cellPaddingWidth = (size.Width - actionsWidth) / (count - 1);
657                     layout.CellPadding = new Size2D(cellPaddingWidth , 0);
658                 }
659             }
660             else
661             {
662                 int actionsHeight = 0;
663
664                 foreach (var action in Actions)
665                 {
666                     actionsHeight += ((View)action).Size2D.Height + ((((View)action).Margin?.Top + ((View)action).Margin?.Bottom) ?? 0);
667                     count++;
668                 }
669
670                 if (count > 1)
671                 {
672                     actionsHeight += (Padding?.Top + Padding?.Bottom) ?? 0;
673                     var cellPaddingHeight = (size.Height - actionsHeight) / (count - 1);
674                     layout.CellPadding = new Size2D(0, cellPaddingHeight);
675                 }
676             }
677         }
678     }
679 }