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