Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web.Mobile / UI / MobileControls / Adapters / ChtmlCalendarAdapter.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="ChtmlCalendarAdapter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 using System;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11 using System.Web;
12 using System.Web.UI;
13 using System.Web.UI.HtmlControls;
14 using System.Web.UI.MobileControls;
15 using System.Web.UI.MobileControls.Adapters;
16 using System.Web.UI.WebControls;
17 using System.Diagnostics;
18 using System.Collections;
19 using System.Security.Permissions;
20
21 #if COMPILING_FOR_SHIPPED_SOURCE
22 namespace System.Web.UI.MobileControls.ShippedAdapterSource
23 #else
24 namespace System.Web.UI.MobileControls.Adapters
25 #endif
26
27 {
28     /*
29      * ChtmlCalendarAdapter provides the cHTML device functionality for Calendar
30      * control.  It is using secondary UI support to provide internal screens
31      * to allow the user to pick or enter a date.
32      *
33      * Copyright (c) 2000 Microsoft Corporation
34      */
35     /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter"]/*' />
36     [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
37     [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
38     [Obsolete("The System.Web.Mobile.dll assembly has been deprecated and should no longer be used. For information about how to develop ASP.NET mobile applications, see http://go.microsoft.com/fwlink/?LinkId=157231.")]
39     public class ChtmlCalendarAdapter : HtmlControlAdapter
40     {
41         private SelectionList _selectList;
42         private TextBox _textBox;
43         private Command _command;
44         private List _optionList;
45         private List _monthList;
46         private List _weekList;
47         private List _dayList;
48         private int _chooseOption = FirstPrompt;
49         private int _monthsToDisplay;
50         private int _eraCount = 0;
51         private bool _requireFormTag = false;
52
53         /////////////////////////////////////////////////////////////////////
54         // Globalization of Calendar Information:
55         // Similar to the globalization support of the ASP.NET Calendar control,
56         // this support is done via COM+ thread culture info/object.
57         // Specific example can be found from ASP.NET Calendar spec.
58         /////////////////////////////////////////////////////////////////////
59
60         // This member variable is set each time when calendar info needs to
61         // be accessed and be shared for other helper functions.
62         private System.Globalization.Calendar _threadCalendar;
63
64         private String _textBoxErrorMessage;
65
66         // Since SecondaryUIMode is an int type, we use constant integers here
67         // instead of enum so the mode can be compared without casting.
68         private const int FirstPrompt = NotSecondaryUIInit;
69         private const int OptionPrompt = NotSecondaryUIInit + 1;
70         private const int TypeDate = NotSecondaryUIInit + 2;
71         private const int DateOption = NotSecondaryUIInit + 3;
72         private const int WeekOption = NotSecondaryUIInit + 4;
73         private const int MonthOption = NotSecondaryUIInit + 5;
74         private const int ChooseMonth = NotSecondaryUIInit + 6;
75         private const int ChooseWeek = NotSecondaryUIInit + 7;
76         private const int ChooseDay = NotSecondaryUIInit + 8;
77         private const int DefaultDateDone = NotSecondaryUIInit + 9;
78         private const int TypeDateDone = NotSecondaryUIInit + 10;
79         private const int Done = NotSecondaryUIInit + 11;
80
81         private const String DaySeparator = " - ";
82         private const String Space = " ";
83
84         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.Control"]/*' />
85         protected new Calendar Control
86         {
87             get
88             {
89                 return (Calendar)base.Control;
90             }
91         }
92
93         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.RequiresFormTag"]/*' />
94         public override bool RequiresFormTag
95         {
96             get
97             {
98                 return _requireFormTag;
99             }
100         }
101
102         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.OnInit"]/*' />
103         public override void OnInit(EventArgs e)
104         {
105             ListCommandEventHandler listCommandEventHandler;
106
107             // Create secondary child controls for rendering secondary UI.
108             // Note that their ViewState is disabled because they are used
109             // for rendering only.
110             //---------------------------------------------------------------
111
112             _selectList = new SelectionList();
113             _selectList.Visible = false;
114             _selectList.EnableViewState = false;
115             Control.Controls.Add(_selectList);
116
117             _textBox = new TextBox();
118             _textBox.Visible = false;
119             _textBox.EnableViewState = false;
120             EventHandler eventHandler = new EventHandler(this.TextBoxEventHandler);
121             _textBox.TextChanged += eventHandler;
122             Control.Controls.Add(_textBox);
123
124             _command = new Command();
125             _command.Visible = false;
126             _command.EnableViewState = false;
127             Control.Controls.Add(_command);
128
129             // Below are initialization of several list controls.  A note is
130             // that here the usage of DataMember is solely for remembering
131             // how many items a particular list control is bounded to.  The
132             // property is not used as originally designed.
133             //---------------------------------------------------------------
134
135             _optionList = new List();
136             _optionList.DataMember = "5";
137             listCommandEventHandler = new ListCommandEventHandler(this.OptionListEventHandler);
138             InitList(_optionList, listCommandEventHandler);
139
140             // Use MobileCapabilities to check screen size and determine how
141             // many months should be displayed for different devices.
142             _monthsToDisplay = MonthsToDisplay(Device.ScreenCharactersHeight);
143
144             // Create the list of months, including [Next] and [Prev] links
145             _monthList = new List();
146             _monthList.DataMember = Convert.ToString(_monthsToDisplay + 2, CultureInfo.InvariantCulture);
147             listCommandEventHandler = new ListCommandEventHandler(this.MonthListEventHandler);
148             InitList(_monthList, listCommandEventHandler);
149
150             _weekList = new List();
151             _weekList.DataMember = "6";
152             listCommandEventHandler = new ListCommandEventHandler(this.WeekListEventHandler);
153             InitList(_weekList, listCommandEventHandler);
154
155             _dayList = new List();
156             _dayList.DataMember = "7";
157             listCommandEventHandler = new ListCommandEventHandler(this.DayListEventHandler);
158             InitList(_dayList, listCommandEventHandler);
159
160             // Initialize the VisibleDate which will be used to keep track
161             // the ongoing selection of year, month and day from multiple
162             // secondary UI screens.  If the page is loaded for the first
163             // time, it doesn't need to be initialized (since it is not used
164             // yet) so no unnecessary viewstate value will be generated.
165             if (Page.IsPostBack && Control.VisibleDate == DateTime.MinValue)
166             {
167                 Control.VisibleDate = DateTime.Today;
168             }
169         }
170
171         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.OnLoad"]/*' />
172         public override void OnLoad(EventArgs e)
173         {
174             base.OnLoad(e);
175
176             // Here we check to see which list control should be initialized
177             // with items so postback event can be handled properly.
178             if (Page.IsPostBack)
179             {
180                 String controlId = Page.Request[Constants.EventSourceID];
181                 if (controlId != null && controlId.Length != 0)
182                 {
183                     List list = Page.FindControl(controlId) as List;
184                     if (list != null &&
185                         Control.Controls.Contains(list))
186                     {
187                         DataBindListWithEmptyValues(
188                             list, Convert.ToInt32(list.DataMember, CultureInfo.InvariantCulture));
189                     }
190                 }
191             }
192         }
193
194         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.LoadAdapterState"]/*' />
195         public override void LoadAdapterState(Object state)
196         {
197             if (state != null)
198             {
199                 if (state is Pair)
200                 {
201                     Pair pair = (Pair)state;
202                     base.LoadAdapterState(pair.First);
203                     _chooseOption = (int)pair.Second;
204                 }
205                 else if (state is Triplet)
206                 {
207                     Triplet triplet = (Triplet)state;
208                     base.LoadAdapterState(triplet.First);
209                     _chooseOption = (int)triplet.Second;
210                     Control.VisibleDate = new DateTime(Int64.Parse((String)triplet.Third, CultureInfo.InvariantCulture));
211                 }
212                 else if (state is Object[])
213                 {
214                     Object[] viewState = (Object[])state;
215                     base.LoadAdapterState(viewState[0]);
216                     _chooseOption = (int)viewState[1];
217                     Control.VisibleDate = new DateTime(Int64.Parse((String)viewState[2], CultureInfo.InvariantCulture));
218                     _eraCount = (int)viewState[3];
219
220                     if (SecondaryUIMode == TypeDate)
221                     {
222                         // Create a placeholder list for capturing the selected era
223                         // in postback data.
224                         for (int i = 0; i < _eraCount; i++)
225                         {
226                             _selectList.Items.Add(String.Empty);
227                         }
228                     }
229                 }
230                 else
231                 {
232                     _chooseOption = (int)state;
233                 }
234             }
235         }
236
237         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.SaveAdapterState"]/*' />
238         public override Object SaveAdapterState()
239         {
240             DateTime visibleDate = Control.VisibleDate;
241
242             bool saveVisibleDate = visibleDate != DateTime.MinValue &&
243                                         DateTime.Compare(visibleDate, DateTime.Today) != 0 && 
244                                         !IsViewStateEnabled();
245             Object baseState = base.SaveAdapterState();
246
247             if (baseState == null && !saveVisibleDate && _eraCount == 0)
248             {
249                 if (_chooseOption != FirstPrompt)
250                 {
251                     return _chooseOption;
252                 }
253                 else
254                 {
255                     return null;
256                 }
257             }
258             else if (!saveVisibleDate && _eraCount == 0)
259             {
260                 return new Pair(baseState, _chooseOption);
261             }
262             else if (_eraCount == 0)
263             {
264                 return new Triplet(baseState, _chooseOption, visibleDate.Ticks.ToString(CultureInfo.InvariantCulture));
265             }
266             else
267             {
268                 return new Object[] { baseState,
269                                       _chooseOption,
270                                       visibleDate.Ticks.ToString(CultureInfo.InvariantCulture),
271                                       _eraCount };
272             }
273         }
274
275         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.OnPreRender"]/*' />
276         public override void OnPreRender(EventArgs e)
277         {
278             base.OnPreRender(e);
279
280             // We specially binding eras of the current calendar object here
281             // when the UI of typing date is display.  We do it only if the
282             // calendar supports more than one era.
283             if (SecondaryUIMode == TypeDate)
284             {
285                 DateTimeFormatInfo currentInfo = DateTimeFormatInfo.CurrentInfo;
286
287                 int [] ints = currentInfo.Calendar.Eras;
288
289                 if (ints.Length > 1)
290                 {
291                     // Save the value in private view state
292                     _eraCount = ints.Length;
293
294                     int currentEra;
295                     if (_selectList.SelectedIndex != -1)
296                     {
297                         currentEra = ints[_selectList.SelectedIndex];
298                     }
299                     else
300                     {
301                         currentEra =
302                             currentInfo.Calendar.GetEra(Control.VisibleDate);
303                     }
304
305                     // Clear the placeholder item list if created in LoadAdapterState
306                     _selectList.Items.Clear();
307
308                     for (int i = 0; i < ints.Length; i++)
309                     {
310                         int era = ints[i];
311
312                         _selectList.Items.Add(currentInfo.GetEraName(era));
313
314                         // There is no association between the era value and
315                         // its index in the era array, so we need to check it
316                         // explicitly for the default selected index.
317                         if (currentEra == era)
318                         {
319                             _selectList.SelectedIndex = i;
320                         }
321                     }
322                     _selectList.Visible = true;
323                 }
324                 else
325                 {
326                     // disable viewstate since no need to save any data for
327                     // this control
328                     _selectList.EnableViewState = false;
329                 }
330             }
331             else
332             {
333                 _selectList.EnableViewState = false;
334             }
335         }
336
337         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.Render"]/*' />
338         public override void Render(HtmlMobileTextWriter writer)
339         {
340             ArrayList arr;
341             DateTime tempDate;
342             DateTimeFormatInfo currentDateTimeInfo = DateTimeFormatInfo.CurrentInfo;
343             String abbreviatedMonthDayPattern = AbbreviateMonthPattern(currentDateTimeInfo.MonthDayPattern);
344             _threadCalendar = currentDateTimeInfo.Calendar;
345             bool breakAfter = false;
346
347             writer.EnterStyle(Style);
348
349             Debug.Assert(NotSecondaryUI == NotSecondaryUIInit);
350             switch (SecondaryUIMode)
351             {
352                 case FirstPrompt:
353                     String promptText = Control.CalendarEntryText;
354                     if (String.IsNullOrEmpty(promptText))
355                     {
356                         promptText = SR.GetString(SR.CalendarAdapterFirstPrompt);
357                     }
358
359                     // Link to input option selection screen
360                     RenderPostBackEventAsAnchor(writer,
361                                                 OptionPrompt.ToString(CultureInfo.InvariantCulture),
362                                                 promptText);
363
364                     // We should honor BreakAfter property here as the first
365                     // UI is shown with other controls on the same form.
366                     // For other secondary UI, it is not necessary.
367                     if (Control.BreakAfter)
368                     {
369                         breakAfter = true;
370                     }
371                     break;
372
373                 // Render the first secondary page that provides differnt
374                 // options to select a date.
375                 case OptionPrompt:
376                     writer.Write(SR.GetString(SR.CalendarAdapterOptionPrompt));
377                     writer.WriteBreak();
378
379                     arr = new ArrayList();
380
381                     // Option to select the default date
382                     arr.Add(Control.VisibleDate.ToString(
383                         currentDateTimeInfo.ShortDatePattern, CultureInfo.CurrentCulture));
384
385                     // Option to another page that can enter a date by typing
386                     arr.Add(SR.GetString(SR.CalendarAdapterOptionType));
387
388                     // Options to a set of pages for selecting a date, a week
389                     // or a month by picking month/year, week and day
390                     // accordingly.  Available options are determined by
391                     // SelectionMode.
392                     arr.Add(SR.GetString(SR.CalendarAdapterOptionChooseDate));
393
394                     if (Control.SelectionMode == CalendarSelectionMode.DayWeek ||
395                         Control.SelectionMode == CalendarSelectionMode.DayWeekMonth)
396                     {
397                         arr.Add(SR.GetString(SR.CalendarAdapterOptionChooseWeek));
398
399                         if (Control.SelectionMode == CalendarSelectionMode.DayWeekMonth)
400                         {
401                             arr.Add(SR.GetString(SR.CalendarAdapterOptionChooseMonth));
402                         }
403                     }
404
405                     DataBindAndRender(writer, _optionList, arr);
406                     break;
407
408                 // Render a title and textbox to capture a date entered by user
409                 case TypeDate:
410                     if (_textBoxErrorMessage != null)
411                     {
412                         writer.Write(_textBoxErrorMessage);
413                         writer.WriteBreak();
414                     }
415
416                     if (_selectList.Visible)
417                     {
418                         writer.Write(SR.GetString(SR.CalendarAdapterOptionEra));
419                         writer.WriteBreak();
420                         _selectList.RenderControl(writer);
421                     }
422
423                     String numericDateFormat = GetNumericDateFormat();
424
425                     writer.Write(SR.GetString(SR.CalendarAdapterOptionType));
426                     writer.Write(":");
427                     writer.WriteBreak();
428                     writer.Write("(");
429                     writer.Write(numericDateFormat.ToUpper(CultureInfo.InvariantCulture));
430                     writer.Write(")");
431
432                     if (!_selectList.Visible)
433                     {
434                         writer.Write(GetEra(Control.VisibleDate));
435                     }
436                     writer.WriteBreak();
437
438                     _textBox.Numeric = true;
439                     _textBox.Size = numericDateFormat.Length;
440                     _textBox.MaxLength = numericDateFormat.Length;
441                     _textBox.Text = Control.VisibleDate.ToString(numericDateFormat, CultureInfo.InvariantCulture);
442                     _textBox.Visible = true;
443                     _textBox.RenderControl(writer);
444
445                     // Command button for sending the textbox value back to the server
446                     _command.Text = GetDefaultLabel(OKLabel);
447                     _command.Visible = true;
448                     _command.RenderControl(writer);
449
450                     break;
451
452                 // Render a paged list for choosing a month
453                 case ChooseMonth:
454                     writer.Write(SR.GetString(SR.CalendarAdapterOptionChooseMonth));
455                     writer.Write(":");
456                     writer.WriteBreak();
457
458                     tempDate = Control.VisibleDate;
459
460                     String abbreviatedYearMonthPattern = AbbreviateMonthPattern(currentDateTimeInfo.YearMonthPattern);
461
462                     // This is to be consistent with ASP.NET Calendar control
463                     // on handling YearMonthPattern:
464                     // Some cultures have a comma in their YearMonthPattern,
465                     // which does not look right in a calendar.  Here we
466                     // strip the comma off.
467                     int indexComma = abbreviatedYearMonthPattern.IndexOf(',');
468                     if (indexComma >= 0)
469                     {
470                         abbreviatedYearMonthPattern =
471                             abbreviatedYearMonthPattern.Remove(indexComma, 1);
472                     }
473
474                     arr = new ArrayList();
475                     for (int i = 0; i < _monthsToDisplay; i++)
476                     {
477                         arr.Add(tempDate.ToString(abbreviatedYearMonthPattern, CultureInfo.CurrentCulture));
478                         tempDate = _threadCalendar.AddMonths(tempDate, 1);
479                     }
480                     arr.Add(GetDefaultLabel(NextLabel));
481                     arr.Add(GetDefaultLabel(PreviousLabel));
482
483                     DataBindAndRender(writer, _monthList, arr);
484                     break;
485
486                 // Based on the month selected in case ChooseMonth above, render a list of
487                 // availabe weeks of the month.
488                 case ChooseWeek:
489                     String monthFormat = (GetNumericDateFormat()[0] == 'y') ? "yyyy/M" : "M/yyyy";
490                     writer.Write(SR.GetString(SR.CalendarAdapterOptionChooseWeek));
491                     writer.Write(" (");
492                     writer.Write(Control.VisibleDate.ToString(monthFormat, CultureInfo.CurrentCulture));
493                     writer.Write("):");
494                     writer.WriteBreak();
495
496                     // List weeks of days of the selected month.  May include
497                     // days from the previous and the next month to fill out
498                     // all six week choices.  This is consistent with the
499                     // ASP.NET Calendar control.
500
501                     // Note that the event handling code of this list control
502                     // should be implemented according to the index content
503                     // generated here.
504
505                     tempDate = FirstCalendarDay(Control.VisibleDate);
506
507                     arr = new ArrayList();
508                     String weekDisplay;
509                     for (int i = 0; i < 6; i++)
510                     {
511                         weekDisplay = tempDate.ToString(abbreviatedMonthDayPattern, CultureInfo.CurrentCulture);
512                         weekDisplay += DaySeparator;
513                         tempDate = _threadCalendar.AddDays(tempDate, 6);
514                         weekDisplay += tempDate.ToString(abbreviatedMonthDayPattern, CultureInfo.CurrentCulture);
515                         arr.Add(weekDisplay);
516                         tempDate = _threadCalendar.AddDays(tempDate, 1);
517                     }
518
519                     DataBindAndRender(writer, _weekList, arr);
520                     break;
521
522                 // Based on the month and week selected in case ChooseMonth and ChooseWeek above,
523                 // render a list of the dates in the week.
524                 case ChooseDay:
525                     writer.Write(SR.GetString(SR.CalendarAdapterOptionChooseDate));
526                     writer.Write(":");
527                     writer.WriteBreak();
528
529                     tempDate = Control.VisibleDate;
530
531                     arr = new ArrayList();
532                     String date;
533                     String dayName;
534                     StringBuilder dayDisplay = new StringBuilder();
535                     bool dayNameFirst = (GetNumericDateFormat()[0] != 'y');
536
537                     for (int i = 0; i < 7; i++)
538                     {
539                         date = tempDate.ToString(abbreviatedMonthDayPattern, CultureInfo.CurrentCulture);
540
541                         if (Control.ShowDayHeader)
542                         {
543                             // Use the short format for displaying day name
544                             dayName = GetAbbreviatedDayName(tempDate);
545                             dayDisplay.Length = 0;
546
547                             if (dayNameFirst)
548                             {
549                                 dayDisplay.Append(dayName);
550                                 dayDisplay.Append(Space);
551                                 dayDisplay.Append(date);
552                             }
553                             else
554                             {
555                                 dayDisplay.Append(date);
556                                 dayDisplay.Append(Space);
557                                 dayDisplay.Append(dayName);
558                             }
559                             arr.Add(dayDisplay.ToString());
560                         }
561                         else
562                         {
563                             arr.Add(date);
564                         }
565                         tempDate = _threadCalendar.AddDays(tempDate, 1);
566                     }
567
568                     DataBindAndRender(writer, _dayList, arr);
569                                         break;
570
571                 default:
572                     Debug.Assert(false, "Unexpected Secondary UI Mode");
573                                         break;
574             }
575
576             writer.ExitStyle(Style, breakAfter);
577         }
578
579         /// <include file='doc\ChtmlCalendarAdapter.uex' path='docs/doc[@for="ChtmlCalendarAdapter.HandlePostBackEvent"]/*' />
580         public override bool HandlePostBackEvent(String eventArgument)
581         {
582             // This is mainly to capture the option picked by the user on
583             // secondary pages and manipulate SecondaryUIMode accordingly so
584             // Render() can generate the appropriate UI.
585             // It also capture the state "Done" which can be set when a date,
586             // a week or a month is selected or entered in some secondary
587             // page.
588
589             SecondaryUIMode = Int32.Parse(eventArgument, CultureInfo.InvariantCulture);
590
591             Debug.Assert(NotSecondaryUI == NotSecondaryUIInit);
592             switch (SecondaryUIMode)
593             {
594             case DefaultDateDone:
595                 SelectRange(Control.VisibleDate, Control.VisibleDate);
596                 goto case Done;
597
598             case TypeDate:
599                 _requireFormTag = true;
600                 break;
601
602             case TypeDateDone:
603                 try
604                 {
605                     String dateText = _textBox.Text;
606                     String dateFormat = GetNumericDateFormat();
607                     DateTimeFormatInfo currentInfo = DateTimeFormatInfo.CurrentInfo;
608                     int eraIndex = _selectList.SelectedIndex;
609
610                     if (eraIndex >= 0 &&
611                         eraIndex < currentInfo.Calendar.Eras.Length)
612                     {
613                         dateText += currentInfo.GetEraName(currentInfo.Calendar.Eras[eraIndex]);
614                         dateFormat += "gg";
615                     }
616
617                     DateTime dateTime = DateTime.ParseExact(dateText, dateFormat, null);
618                     SelectRange(dateTime, dateTime);
619                     Control.VisibleDate = dateTime;
620                 }
621                 catch
622                 {
623                     _textBoxErrorMessage = SR.GetString(SR.CalendarAdapterTextBoxErrorMessage);
624                     SecondaryUIMode = TypeDate;
625                     goto case TypeDate;
626                 }
627                 goto case Done;
628
629             case Done:
630                 // Set the secondary exit code and raise the selection event for
631                 // web page developer to manipulate the selected date.
632                 ExitSecondaryUIMode();
633                 _chooseOption = FirstPrompt;
634                 break;
635
636             case DateOption:
637             case WeekOption:
638             case MonthOption:
639                 _chooseOption = SecondaryUIMode;  // save in the ViewState
640
641                 // In all 3 cases, continue to the UI that chooses a month
642                 SecondaryUIMode = ChooseMonth;
643                 break;
644             }
645
646             return true;
647         }
648
649         /////////////////////////////////////////////////////////////////////
650         // Misc. helper and wrapper functions
651         /////////////////////////////////////////////////////////////////////
652
653         private int MonthsToDisplay(int screenCharactersHeight)
654         {
655             const int MinMonthsToDisplay = 4;
656             const int MaxMonthsToDisplay = 12;
657
658             if (screenCharactersHeight < MinMonthsToDisplay)
659             {
660                 return MinMonthsToDisplay;
661             }
662             else if (screenCharactersHeight > MaxMonthsToDisplay)
663             {
664                 return MaxMonthsToDisplay;
665             }
666             return screenCharactersHeight;
667         }
668
669         // A helper function to initialize and add a child list control
670         private void InitList(List list,
671                               ListCommandEventHandler eventHandler)
672         {
673             list.Visible = false;
674             list.ItemCommand += eventHandler;
675             list.EnableViewState = false;
676             Control.Controls.Add(list);
677         }
678
679         private void DataBindListWithEmptyValues(List list, int arraySize)
680         {
681             ArrayList arr = new ArrayList();
682             for (int i = 0; i < arraySize; i++)
683             {
684                 arr.Add("");
685             }
686             list.DataSource = arr;
687             list.DataBind();
688         }
689
690         // A helper function to do the common code for DataBind and
691         // RenderChildren.
692         private void DataBindAndRender(HtmlMobileTextWriter writer,
693                                        List list,
694                                        ArrayList arr)
695         {
696             list.DataSource = arr;
697             list.DataBind();
698             list.Visible = true;
699             list.RenderControl(writer);
700         }
701
702         // Abbreviate the Month format from "MMMM" (full
703         // month name) to "MMM" (three-character month abbreviation)
704         private String AbbreviateMonthPattern(String pattern)
705         {
706             const String FullMonthFormat = "MMMM";
707
708             int i = pattern.IndexOf(FullMonthFormat, StringComparison.Ordinal);
709             if (i != -1)
710             {
711                 pattern = pattern.Remove(i, 1);
712             }
713             return pattern;
714         }
715
716         private String GetAbbreviatedDayName(DateTime dateTime)
717         {
718             return DateTimeFormatInfo.CurrentInfo.GetAbbreviatedDayName(
719                         _threadCalendar.GetDayOfWeek(dateTime));
720         }
721
722         private String GetEra(DateTime dateTime)
723         {
724             // We shouldn't need to display the era for the common Gregorian
725             // Calendar
726             if (DateTimeFormatInfo.CurrentInfo.Calendar.GetType() ==
727                 typeof(GregorianCalendar))
728             {
729                 return String.Empty;
730             }
731             else
732             {
733                 return dateTime.ToString("gg", CultureInfo.CurrentCulture);
734             }
735         }
736
737         private static readonly char[] formatChars =
738                                             new char[] { 'M', 'd', 'y' };
739
740         private String GetNumericDateFormat()
741         {
742             String shortDatePattern =
743                 DateTimeFormatInfo.CurrentInfo.ShortDatePattern;
744
745             // Guess on what short date pattern should be used
746             int i = shortDatePattern.IndexOfAny(formatChars);
747
748             char firstFormatChar;
749             if (i == -1)
750             {
751                 firstFormatChar = 'M';
752             }
753             else
754             {
755                 firstFormatChar = shortDatePattern[i];
756             }
757
758             // We either use two or four digits for the year
759             String yearPattern;
760             if (shortDatePattern.IndexOf("yyyy", StringComparison.Ordinal) == -1)
761             {
762                 yearPattern = "yy";
763             }
764             else
765             {
766                 yearPattern = "yyyy";
767             }
768
769             switch (firstFormatChar)
770             {
771             case 'M':
772             default:
773                 return "MMdd" + yearPattern;
774             case 'd':
775                 return "ddMM" + yearPattern;
776             case 'y':
777                 return yearPattern + "MMdd";
778             }
779         }
780
781         /////////////////////////////////////////////////////////////////////
782         // Helper functions
783         /////////////////////////////////////////////////////////////////////
784
785         // Return the first date of the input year and month
786         private DateTime EffectiveVisibleDate(DateTime visibleDate)
787         {
788             return _threadCalendar.AddDays(
789                         visibleDate,
790                         -(_threadCalendar.GetDayOfMonth(visibleDate) - 1));
791         }
792
793         // Return the beginning date of a calendar that includes the
794         // targeting month.  The date can actually be in the previous month.
795         private DateTime FirstCalendarDay(DateTime visibleDate)
796         {
797             DateTime firstDayOfMonth = EffectiveVisibleDate(visibleDate);
798             int daysFromLastMonth =
799                 ((int)_threadCalendar.GetDayOfWeek(firstDayOfMonth)) -
800                 NumericFirstDayOfWeek();
801
802             // Always display at least one day from the previous month
803             if (daysFromLastMonth <= 0)
804             {
805                 daysFromLastMonth += 7;
806             }
807             return _threadCalendar.AddDays(firstDayOfMonth, -daysFromLastMonth);
808         }
809
810         private int NumericFirstDayOfWeek()
811         {
812             // Used globalized value by default
813             return(Control.FirstDayOfWeek == FirstDayOfWeek.Default)
814             ? (int) DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek
815             : (int) Control.FirstDayOfWeek;
816         }
817
818         /////////////////////////////////////////////////////////////////////
819         // The followings are event handlers to capture the selection from
820         // the corresponding list control in an secondary page.  The index of
821         // the selection is used to determine which and how the next
822         // secondary page is rendered.  Some event handlers below update
823         // Calendar.VisibleDate and set SecondaryUIMode with appropriate
824         // values.
825         ////////////////////////////////////////////////////////////////////////
826
827         private void TextBoxEventHandler(Object source, EventArgs e)
828         {
829             HandlePostBackEvent(TypeDateDone.ToString(CultureInfo.InvariantCulture));
830         }
831
832         private static readonly int[] Options =
833             {DefaultDateDone, TypeDate, DateOption, WeekOption, MonthOption};
834
835         private void OptionListEventHandler(Object source, ListCommandEventArgs e)
836         {
837             SecondaryUIMode = Options[e.ListItem.Index];
838             HandlePostBackEvent(SecondaryUIMode.ToString(CultureInfo.InvariantCulture));
839         }
840
841         private void MonthListEventHandler(Object source, ListCommandEventArgs e)
842         {
843             _threadCalendar = DateTimeFormatInfo.CurrentInfo.Calendar;
844
845             if (e.ListItem.Index == _monthsToDisplay)
846             {
847                 // Next was selected
848                 Control.VisibleDate = _threadCalendar.AddMonths(
849                                         Control.VisibleDate, _monthsToDisplay);
850                 SecondaryUIMode = ChooseMonth;
851             }
852             else if (e.ListItem.Index == _monthsToDisplay + 1)
853             {
854                 // Prev was selected
855                 Control.VisibleDate = _threadCalendar.AddMonths(
856                                         Control.VisibleDate, -_monthsToDisplay);
857                 SecondaryUIMode = ChooseMonth;
858             }
859             else
860             {
861                 // A month was selected
862                 Control.VisibleDate = _threadCalendar.AddMonths(
863                                         Control.VisibleDate,
864                                         e.ListItem.Index);
865
866                 if (_chooseOption == MonthOption)
867                 {
868                     // Add the whole month to the date list
869                     DateTime beginDate = EffectiveVisibleDate(Control.VisibleDate);
870                     Control.VisibleDate = beginDate;
871
872                     DateTime endDate = _threadCalendar.AddMonths(beginDate, 1);
873                     endDate = _threadCalendar.AddDays(endDate, -1);
874
875                     SelectRange(beginDate, endDate);
876                     HandlePostBackEvent(Done.ToString(CultureInfo.InvariantCulture));
877                 }
878                 else
879                 {
880                     SecondaryUIMode = ChooseWeek;
881                 }
882             }
883         }
884
885         private void WeekListEventHandler(Object source, ListCommandEventArgs e)
886         {
887             // Get the first calendar day and adjust it to the week the user
888             // selected (to be consistent with the index setting in Render())
889             _threadCalendar = DateTimeFormatInfo.CurrentInfo.Calendar;
890
891             DateTime tempDate = FirstCalendarDay(Control.VisibleDate);
892
893             Control.VisibleDate = _threadCalendar.AddDays(tempDate, e.ListItem.Index * 7);
894
895             if (_chooseOption == WeekOption)
896             {
897                 // Add the whole week to the date list
898                 DateTime endDate = _threadCalendar.AddDays(Control.VisibleDate, 6);
899
900                 SelectRange(Control.VisibleDate, endDate);
901                 HandlePostBackEvent(Done.ToString(CultureInfo.InvariantCulture));
902             }
903             else
904             {
905                 SecondaryUIMode = ChooseDay;
906             }
907         }
908
909         private void DayListEventHandler(Object source, ListCommandEventArgs e)
910         {
911             _threadCalendar = DateTimeFormatInfo.CurrentInfo.Calendar;
912
913             // VisibleDate should have been set with the first day of the week
914             // so the selected index can be used to adjust to the selected day.
915             Control.VisibleDate = _threadCalendar.AddDays(Control.VisibleDate, e.ListItem.Index);
916
917             SelectRange(Control.VisibleDate, Control.VisibleDate);
918             HandlePostBackEvent(Done.ToString(CultureInfo.InvariantCulture));
919         }
920
921         private void SelectRange(DateTime dateFrom, DateTime dateTo)
922         {
923             Debug.Assert(dateFrom <= dateTo, "Bad Date Range");
924
925             // see if this range differs in any way from the current range
926             // these checks will determine this because the colleciton is sorted
927             TimeSpan ts = dateTo - dateFrom;
928             SelectedDatesCollection selectedDates = Control.SelectedDates;
929             if (selectedDates.Count != ts.Days + 1 
930                 || selectedDates[0] != dateFrom
931                 || selectedDates[selectedDates.Count - 1] != dateTo)
932             {
933                 selectedDates.SelectRange(dateFrom, dateTo);
934                 Control.RaiseSelectionChangedEvent();
935             }
936         }
937
938         private bool IsViewStateEnabled()
939         {
940             Control ctl = Control;
941             while (ctl != null)
942             {
943                 if (!ctl.EnableViewState)
944                 {
945                     return false;
946                 }
947                 ctl = ctl.Parent;
948             }
949             return true;
950         }
951     }
952 }