    using System;
    using System.Data;
    using System.Drawing;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.MobileControls;
    using System.Web.UI.MobileControls.Adapters;
    using ASPNET.StarterKit.Portal.MobileControls;

    [ assembly:TagPrefix("ASPNET.StarterKit.Portal.MobileControls", "portal") ]

    namespace ASPNET.StarterKit.Portal {

        //*********************************************************************
        //
        // MobilePortalModuleControl
        //
        // The MobilePortalModuleControl class is the base class used for
        // each module user control in the mobile portal. Since it implements
        // the IContentsPane interface, any control inheriting from this class
        // can be used as a module in a portal tab.
        //
        //*********************************************************************

        public class MobilePortalModuleControl : UserControl, IContentsPane {

            private ModuleSettings _moduleConfiguration;
            private Control _summaryControl;

            //*********************************************************************
            //
            // MobilePortalModuleControl.ModuleConfiguration Property
            //
            // Returns the configuration information for this module.
            //
            //*********************************************************************

            public ModuleSettings ModuleConfiguration {

                get {
                    return _moduleConfiguration;
                }
                set {
                    _moduleConfiguration = value;
                }
            }

            //*********************************************************************
            //
            // MobilePortalModuleControl.Tab Property
            //
            // Returns the parent portal tab.
            //
            //*********************************************************************

            public MobilePortalTab Tab {

                get {
                    return Parent as MobilePortalTab;
                }
            }

            //*********************************************************************
            //
            // MobilePortalModuleControl.ModuleTitle Property
            //
            // Returns the name of this module.
            //
            //*********************************************************************

            [Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public String ModuleTitle {

                get {
                    return _moduleConfiguration.ModuleTitle;
                }
            }

            //*********************************************************************
            //
            // MobilePortalModuleControl.ModuleId Property
            //
            // Returns the unique ID of this module.
            //
            //*********************************************************************

            [Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public int ModuleId {

                get {
                    return _moduleConfiguration.ModuleId;
                }
            }

            //*********************************************************************
            //
            // IContentsPane.Title Property
            //
            // Returns the name of the module, to be used as the pane title
            // when used inside a tab.
            //
            //*********************************************************************

            String IPanelPane.Title {

                get {
                    return _moduleConfiguration.ModuleTitle;
                }
            }

            //*********************************************************************
            //
            // IContentsPane.OnSetSummaryMode Method
            //
            // OnSetSummaryMode is called on each child pane when the parent tab
            // changes from showing summaries to individual details or vice versa.
            // This method calls the UpdateVisibility utility method to
            // update the visibility of child controls.
            // REVIEW: Probably could be done using an event handler instead.
            //
            //*********************************************************************

            void IContentsPane.OnSetSummaryMode() {

                UpdateVisibility();
            }

            //*********************************************************************
            //
            // MobilePortalModuleControl.OnInit Method
            //
            // OnInit is called when the control is created and added to the
            // control tree. OnInit looks for a child control that renders the
            // summary view of the module, and creates a default one (with a
            // simple LinkCommand control) if no summary is found.
            //
            //*********************************************************************

            protected override void OnInit(EventArgs e) {

                base.OnInit(e);

                // Look for a control that renders the summary.
                _summaryControl = FindControl("summary");

                // There could be no summary control, or the summary control may be
                // an empty panel. If there's no summary UI, automatically generate one.
                if (_summaryControl == null || (_summaryControl is Panel && !_summaryControl.HasControls())) {

                    // Create and initialize a new LinkCommand control
                    Command command = new LinkCommand();
                    command.Text = this.ModuleTitle;

                    // Set the command name to the details command, so that
                    // event bubbling can recognize it as a command to go to
                    // details view.
                    command.CommandName = ContentsPanel.DetailsCommand;

                    // Add it to the appropriate place.
                    if (_summaryControl != null) {

                        _summaryControl.Controls.Add(command);
                    }
                    else {

                        Controls.Add(command);
                        _summaryControl = command;
                    }
                }
            }

            //*********************************************************************
            //
            // MobilePortalModuleControl.OnLoad Method
            //
            // OnLoad is called when the control is created and added to the
            // control tree, after OnInit. OnLoad calls the UpdateVisibility
            // utility method to update the visibility of child controls.
            //
            //*********************************************************************

            protected override void OnLoad(EventArgs e) {

                base.OnLoad(e);
                UpdateVisibility();
            }

            //*********************************************************************
            //
            // MobilePortalModuleControl.UpdateVisibility Method
            //
            // UpdateVisibility updates the visibility of child controls
            // depending on the current setting. If the module is currently
            // being shown in summary mode, all children except the summary
            // control are hidden. If the module is currently being shown
            // in details mode, only the summary control is hidden.
            //
            //*********************************************************************

            private void UpdateVisibility() {

                bool summary = Tab != null && Tab.SummaryView;

                foreach (Control child in Controls) {
                    child.Visible = !summary;
                }

                if (_summaryControl != null) {
                    _summaryControl.Visible = summary;
                }
            }
        }

        //*********************************************************************
        //
        // MobilePortalTab Class
        //
        // The MobilePortalTab class is used for each tab of the mobile
        // portal.
        //
        //*********************************************************************

        public class MobilePortalTab : ContentsPanel {
        }
    }

    namespace ASPNET.StarterKit.Portal.MobileControls {

        //*********************************************************************
        //
        // LinkCommand Class
        //
        // The LinkCommand class is used for a simple custom version of the
        // Command control. Although the class itself has no added or modified
        // functionality, it allows a new adapter to be specified. On
        // HTML devices, this control renders as a hyperlink rather than
        // a button.
        //
        //*********************************************************************

        public class LinkCommand : Command {
        }

        //*********************************************************************
        //
        // HtmlLinkCommandAdapter Class
        //
        // The HtmlLinkCommandAdapter class is used to render the LinkCommand
        // control on an HTML device. Unlike the Command control, which renders
        // as a button, the HtmlLinkCommandAdapter renders a LinkCommand as
        // a hyperlink. Only the Render method needs to be overriden.
        //
        //*********************************************************************

        public class HtmlLinkCommandAdapter : HtmlCommandAdapter {

            //*********************************************************************
            //
            // HtmlLinkCommandAdapter.Render Method
            //
            // The Render method performs rendering of the LinkCommand control.
            //
            //*********************************************************************

            public override void Render(HtmlMobileTextWriter writer) {
                // Render a postback event as an anchor.
                RenderPostBackEventAsAnchor(writer, null, Control.Text);

                // Write a break, if necessary.
                writer.WriteBreak();
            }
        }

        //*********************************************************************
        //
        // Panels Package
        //
        // The Panels Package is a set of bonus mobile controls used for
        // the Portal Starter Kit. The package provides a new set of
        // control classes. All of these controls inherit from the
        // System.Web.UI.MobileControls.Panel class.
        //
        //      MultiPanel
        //          A base class capable of managing multiple child controls,
        //          called "panes". Each child pane must implement the
        //          IPanelPane interface.
        //      ChildPanel
        //          A base class for panels that can be used as child panes
        //          of MultiPanel panels. MultiPanel itself inherits from
        //          ChildPanel, so you can nest one MultiPanel as a child
        //          pane of another.
        //      TabbedPanel
        //          A specialized type of MultiPanel that comes with
        //          adapters for rendering the panel as a tab view where
        //          appropriate. On other devices, adapters render the
        //          TabbedPanel using a separate menu screen.
        //      ContentsPanel
        //          A specialized type of MultiPanel that can show either
        //          a summary view, where all child panes are shown
        //          simultaneously, or a details view that shows the
        //          active pane. Each child pane must implement the
        //          IContentsPane interface.
        //
        // Although these controls are fairly advanced compared to the
        // rest of the portal, full source code is provided.
        //
        //*********************************************************************

        //*********************************************************************
        //
        // IPanelPane interface
        //
        // The IPanelPane interface must be implemented by any control
        // that needs to be a child pane of a MultiPanel or derivative
        // control. The interface has the following members:
        //
        //      Title property
        //          Returns the title of the pane.
        //
        //*********************************************************************

        public interface IPanelPane {
            String Title { get; }
        }

        //*********************************************************************
        //
        // IContentsPane interface
        //
        // The IContentsPane interface must be implemented by any control
        // that needs to be a child pane of a ContentsPanel control.
        // The interface has the following members:
        //
        //      Title property
        //          Returns the title of the pane.
        //      OnSetSummaryMode method
        //          Called when the ContentsPane control switches
        //          from summary view to item details view.
        //
        //*********************************************************************

        public interface IContentsPane : IPanelPane {

            void OnSetSummaryMode();
        }

        //*********************************************************************
        //
        // ChildPanel Class
        //
        // The ChildPanel Class is a control that inherits from
        // System.Web.UI.MobileControls.Panel, and can be placed inside
        // a MultiPanel control. Even MultiPanel inherits from ChildPanel,
        // allowing nesting of MultiPanel controls.
        //
        //*********************************************************************

        public class ChildPanel : Panel, IPanelPane, INamingContainer {
            //*********************************************************************
            //
            // IPanelPane.Title Property
            //
            // Returns the title of the pane.
            //
            //*********************************************************************

            String IPanelPane.Title {
                get {
                    return this.Title;
                }
            }

            //*********************************************************************
            //
            // ChildPanel.Title Property
            //
            // Returns the title of the pane.
            //
            //*********************************************************************

            public String Title {
                get {
                    // Load the title from the ViewState property bag,
                    // defaulting to an empty String.
                    String s = (String)ViewState["Title"];
                    return s != null ? s : String.Empty;
                }

                set {
                    // Save the title to the ViewState property bag.
                    ViewState["Title"] = value;
                }
            }

            //*********************************************************************
            //
            // ChildPanel.PaginateChildren Property
            //
            // The PaginateChildren property controls whether the form
            // can paginate children of the panel individually. Overriden
            // to allow contents to be paginated.
            //
            //*********************************************************************

            protected override bool PaginateChildren {
                get {
                    return true;
                }
            }
        }

        //*********************************************************************
        //
        // MultiPanel Class
        //
        // The MultiPanel Class is a control that inherits from
        // ChildPanel, and can manage one or more child controls or "panes".
        //
        //*********************************************************************

        public class MultiPanel : ChildPanel {
            // Collection of panes.
            private PanelPaneCollection _panes;

            //*********************************************************************
            //
            // MultiPanel.Panes Property
            //
            // Returns the collection of child panes.
            //
            //*********************************************************************

            public PanelPaneCollection Panes {
                get {
                    // If not yet created, create the collection.
                    if (_panes == null) {
                        _panes = new PanelPaneCollection(this);
                    }
                    return _panes;
                }
            }

            //*********************************************************************
            //
            // MultiPanel.ActivePane Property
            //
            // Get or set the currently active child pane.
            //
            //*********************************************************************

            public IPanelPane ActivePane {
                get {
                    // Get the index of the active pane, and use it to
                    // look up the active pane.
                    int index = ActivePaneIndex;
                    return (index != -1) ? Panes[index] : null;
                }

                set {
                    // Find the index of the given pane, and use it to
                    // set the active pane index.
                    int index = Panes.IndexOf(value);
                    if (index == -1) {
                        throw new Exception("Pane not in Panes collection");
                    }
                    ActivePaneIndex = index;
                }
            }

            //*********************************************************************
            //
            // MultiPanel.ActivePaneIndex Property
            //
            // Get or set the index of the currently active child pane.
            //
            //*********************************************************************

            public int ActivePaneIndex {
                get {
                    // Get the index from the ViewState property bag, defaulting
                    // to the first pane if not found.
                    Object o = ViewState["ActivePaneIndex"];
                    if (o != null) {
                        return (int)o;
                    }
                    else {
                        return (Panes.Count > 0) ? 0 : -1;
                    }
                }

                set {
                    // Make sure index is within range.
                    if (value < 0 || value >= Panes.Count) {
                        throw new Exception("Active pane index out of range");
                    }

                    // Set the index in the ViewState property bag.
                    ViewState["ActivePaneIndex"] = value;
                }
            }

            //*********************************************************************
            //
            // MultiPanel.AddParsedSubObject Method
            //
            // AddParsedSubObject is called by the framework when a child
            // control is being added to the control from the persistence format.
            // AddParsedSubObject below checks if the added control is a
            // child pane, and automatically adds it to the Panes collection.
            //
            //*********************************************************************

            protected override void AddParsedSubObject(Object obj) {
                IPanelPane pane = obj as IPanelPane;

                // Only allow panes as children.
                if (pane == null) {
                    throw new Exception("A MultiPanel control can only contain panes.");
                }

                // Add the pane to the Panes collection.
                Panes.AddInternal(pane);
                base.AddParsedSubObject(obj);
            }

            //*********************************************************************
            //
            // MultiPanel.OnRender Method
            //
            // OnRender is called by the framework to render the control.
            // By default, OnRender of a MultiPanel only renders the active
            // child pane. Specialized versions of the control, such as
            // TabbedPanel and ContentsPanel, have different behavior.
            //
            //*********************************************************************

            protected override void OnRender(HtmlTextWriter writer) {
                ((Control)ActivePane).RenderControl(writer);
            }

            //*********************************************************************
            //
            // MultiPanel.PaginateRecursive Method
            //
            // PaginateRecursive is called by the framework to recursively
            // paginate children. For MultiPanel controls, PaginateRecursive
            // only paginates the active child pane.
            //
            //*********************************************************************

            public override void PaginateRecursive(ControlPager pager) {
                Control activePane = (Control)ActivePane;

                // Active pane may not be a mobile control (e.g. it may be
                // a user control).
                MobileControl mobileCtl = activePane as MobileControl;

                if (mobileCtl != null) {
                    // Paginate the children.
                    mobileCtl.PaginateRecursive(pager);

                    // Set own first and last page from results of child
                    // pagination.
                    this.FirstPage = mobileCtl.FirstPage;
                    this.LastPage = pager.PageCount;
                }
                else {
                    // Call the DoPaginateChildren utility method to
                    // paginate a non-mobile child.
                    int firstAssignedPage = -1;
                    DoPaginateChildren(pager, activePane, ref firstAssignedPage);

                    // Set own first and last page from results of child
                    // pagination.
                    if (firstAssignedPage != -1) {
                        this.FirstPage = firstAssignedPage;
                    }
                    else {
                        this.FirstPage = pager.GetPage(100);
                    }
                    this.LastPage = pager.PageCount;
                }
            }

            //*********************************************************************
            //
            // MultiPanel.DoPaginateRecursive Static Method
            //
            // The DoPaginateRecursive method paginates non-mobile child
            // controls, looking for mobile controls inside them.
            //
            //*********************************************************************

            private static void DoPaginateChildren(ControlPager pager, Control ctl, ref int firstAssignedPage) {
                // Search all children of the control.
                if (ctl.HasControls()) {
                    foreach (Control child in ctl.Controls) {
                        if (child.Visible) {
                            // Look for a visible mobile control.
                            MobileControl mobileCtl = child as MobileControl;
                            if (mobileCtl != null) {
                                // Paginate the mobile control.
                                mobileCtl.PaginateRecursive(pager);

                                // If this is the first control being paginated,
                                // set the first assigned page.
                                if (firstAssignedPage == -1) {
                                    firstAssignedPage = mobileCtl.FirstPage;
                                }
                            }
                            else if (child is UserControl) {
                                // Continue paginating user controls, which may contain
                                // their own mobile children.
                                DoPaginateChildren(pager, child, ref firstAssignedPage);
                            }
                        }
                    }
                }
            }

        }

        //*********************************************************************
        //
        // PanelPaneCollection Class
        //
        // The PanelPaneCollection Class is used to keep a collection of
        // child panes of a MultiPanel control. The class implements
        // ICollection, so it can be used as a general collection.
        //
        //*********************************************************************

        public class PanelPaneCollection : ICollection {
            // Private instance variables.
            private MultiPanel _parent;
            private ArrayList _items = new ArrayList();

            // Can only be instantiated by MultiPanel.
            internal PanelPaneCollection(MultiPanel parent) {
                // Save off reference to parent control.
                _parent = parent;
            }

            //*********************************************************************
            //
            // PanelPaneCollection.Add Method
            //
            // Adds a pane to the collection.
            //
            //*********************************************************************

            public void Add(IPanelPane pane) {
                // Add the pane to the parent's child controls collection.
                _parent.Controls.Add((Control)pane);
                _items.Add(pane);
            }

            //*********************************************************************
            //
            // PanelPaneCollection.AddInternal Method
            //
            // Adds a pane to the collection, but does not add it to the parent's
            // controls. This is called by the parent control itself to add
            // panes.
            //
            //*********************************************************************

            internal void AddInternal(IPanelPane pane) {
                _items.Add(pane);
            }

            //*********************************************************************
            //
            // PanelPaneCollection.Remove Method
            //
            // Removes a pane from the collection.
            //
            //*********************************************************************

            public void Remove(IPanelPane pane) {
                // Remove the pane from the parent's child controls collection.
                _parent.Controls.Remove((Control)pane);
                _items.Remove(pane);
            }

            //*********************************************************************
            //
            // PanelPaneCollection.Clear Method
            //
            // Removes all panes from the collection.
            //
            //*********************************************************************

            public void Clear() {
                // Remove all child controls from the parent.
                foreach (Control pane in _items) {
                    _parent.Controls.Remove(pane);
                }
                _items.Clear();
            }

            //*********************************************************************
            //
            // PanelPaneCollection.this[] Property
            //
            // Returns a pane by index.
            //
            //*********************************************************************

            public IPanelPane this[int index] {
                get {
                    return (IPanelPane)_items[index];
                }
            }

            //*********************************************************************
            //
            // PanelPaneCollection.Count Property
            //
            // Returns the number of panes in the collection.
            //
            //*********************************************************************

            public int Count {
                get {
                    return _items.Count;
                }
            }

            //*********************************************************************
            //
            // PanelPaneCollection.IndexOf Method
            //
            // Returns the index of a given pane.
            //
            //*********************************************************************

            public int IndexOf(IPanelPane pane) {
                return _items.IndexOf(pane);
            }

            //*********************************************************************
            //
            // PanelPaneCollection.IsReadOnly Property
            //
            // Returns whether the collection is read-only.
            //
            //*********************************************************************

            public bool IsReadOnly {
                get {
                    return _items.IsReadOnly;
                }
            }

            //*********************************************************************
            //
            // PanelPaneCollection.IsSynchronized Property
            //
            // Returns whether the collection is synchronized.
            //
            //*********************************************************************

            public bool IsSynchronized {
                get {
                    return false;
                }
            }

            //*********************************************************************
            //
            // PanelPaneCollection.SyncRoot Property
            //
            // Returns the collection's synchronization root.
            //
            //*********************************************************************

            public Object SyncRoot {
                get {
                    return this;
                }
            }

            //*********************************************************************
            //
            // PanelPaneCollection.CopyTo Method
            //
            // Copies the contents of the collection to an array.
            //
            //*********************************************************************

            public void CopyTo(Array array, int index) {
                foreach (Object item in _items) {
                    array.SetValue (item, index++);
                }
            }

            //*********************************************************************
            //
            // PanelPaneCollection.GetEnumerator Method
            //
            // Returns an object capable of enumerating the collection.
            //
            //*********************************************************************

            public virtual IEnumerator GetEnumerator() {
                return _items.GetEnumerator ();
            }
        }

        //*********************************************************************
        //
        // TabbedPanel Class
        //
        // The TabbedPanel Class is a control that inherits from MultiPanel,
        // and provides the ability for the user to switch between panels.
        // The TabbedPanel also has adapters defined for custom rendering.
        //
        //*********************************************************************

        public class TabbedPanel : MultiPanel, IPostBackEventHandler {
            //*********************************************************************
            //
            // TabbedPanel.OnRender Method
            //
            // OnRender is called by the framework to render the control.
            // The TabbedPanel's OnRender method overrides the behavior
            // of MultiPanel, and directly calls the adapter to do rendering.
            //
            //*********************************************************************

            protected override void OnRender(HtmlTextWriter writer) {
                Adapter.Render(writer);
            }

            //*********************************************************************
            //
            // TabbedPanel.TabColor Property
            //
            // Gets or sets the background color used for each tab label, when
            // tabbed rendering is used.
            //
            //*********************************************************************

            public Color TabColor {
                get {
                    // Get the color from the ViewState property bag, defaulting
                    // to an empty color.
                    Object o = ViewState["TabColor"];
                    return o != null ? (Color)o : Color.Empty;
                }

                set {
                    // Save the color in the ViewState property bag.
                    ViewState["TabColor"] = value;
                }
            }

            //*********************************************************************
            //
            // TabbedPanel.TabTextColor Property
            //
            // Gets or sets the text color used for each tab label, when
            // tabbed rendering is used.
            //
            //*********************************************************************

            public Color TabTextColor {
                get {
                    // Get the color from the ViewState property bag, defaulting
                    // to an empty color.
                    Object o = ViewState["TabTextColor"];
                    return o != null ? (Color)o : Color.Empty;
                }

                set {
                    // Save the color in the ViewState property bag.
                    ViewState["TabTextColor"] = value;
                }
            }

            //*********************************************************************
            //
            // TabbedPanel.ActiveTabColor Property
            //
            // Gets or sets the background color used for the active tab label, when
            // tabbed rendering is used.
            //
            //*********************************************************************

            public Color ActiveTabColor {
                get {
                    // Get the color from the ViewState property bag, defaulting
                    // to an empty color.
                    Object o = ViewState["ActiveTabColor"];
                    return o != null ? (Color)o : Color.Empty;
                }

                set {
                    // Save the color in the ViewState property bag.
                    ViewState["ActiveTabColor"] = value;
                }
            }

            //*********************************************************************
            //
            // TabbedPanel.ActiveTabTextColor Property
            //
            // Gets or sets the text color used for the active tab label, when
            // tabbed rendering is used.
            //
            //*********************************************************************

            public Color ActiveTabTextColor {
                get {
                    // Get the color from the ViewState property bag, defaulting
                    // to an empty color.
                    Object o = ViewState["ActiveTabTextColor"];
                    return o != null ? (Color)o : Color.Empty;
                }

                set {
                    // Save the color in the ViewState property bag.
                    ViewState["ActiveTabTextColor"] = value;
                }
            }

            //*********************************************************************
            //
            // TabbedPanel.TabsPerRow Property
            //
            // Gets or sets the number of tabs to be displayed per row, when
            // tabbed rendering is used.
            //
            //*********************************************************************

            public int TabsPerRow {
                get {
                    // Get the value from the ViewState property bag, defaulting
                    // to 4.
                    Object o = ViewState["TabsPerRow"];
                    return o != null ? (int)o : 4;
                }

                set {
                    // Save the value in the ViewState property bag.
                    ViewState["TabsPerRow"] = value;
                }
            }

            //*********************************************************************
            //
            // IPostBackEventHandler.RaisePostBackEvent Property
            //
            // RaisePostBackEvent is called by the framework when the control
            // is to receive a postback event. Responds to the event by
            // using the event information to switch to another active pane.
            //
            //*********************************************************************

            public virtual void RaisePostBackEvent(String eventArgument) {
                EventArgs e = new EventArgs();

                // Call Deactivate event handler.
                OnTabDeactivate(e);

                ActivePaneIndex = Int32.Parse(eventArgument);

                // Call Activate event handler.
                OnTabActivate(e);
            }

            // Public events.
            public event EventHandler TabActivate;
            public event EventHandler TabDeactivate;

            //*********************************************************************
            //
            // IPostBackEventHandler.OnTabActivate Method
            //
            // OnTabActivate is called when a child pane is newly activated
            // as a result of user interaction, and raises the TabActivate event.
            //
            //*********************************************************************

            protected virtual void OnTabActivate(EventArgs e) {
                if (TabActivate != null) {
                    TabActivate(this, e);
                }
            }

            //*********************************************************************
            //
            // IPostBackEventHandler.OnTabDeactivate Method
            //
            // OnTabDeactivate is called when a child pane is deactivated
            // as a result of user interaction, and raises the TabDeactivate event.
            //
            //*********************************************************************

            protected virtual void OnTabDeactivate(EventArgs e) {
                if (TabDeactivate != null) {
                    TabDeactivate(this, e);
                }
            }
        }

        //*********************************************************************
        //
        // ContentsPanel Class
        //
        // The ContentsPanel Class is a control that inherits from MultiPanel,
        // and can render child panes in one of two views. In Summary View,
        // the control renders each of its child panes (which, in turn, would
        // probably show only summarized views of themselves) In Details View
        // the control only renders the active pane.
        //
        //*********************************************************************

        public class ContentsPanel : MultiPanel {
            // Constants for command names that can be used for
            // event bubbling in custom UI.
            public static readonly String DetailsCommand = "details";
            public static readonly String SummaryCommand = "summary";

            //*********************************************************************
            //
            // ContentsPanel.SummaryView Property
            //
            // Get or set the view of the panel to either Summary (true)
            // or Details (false) view.
            //
            //*********************************************************************

            public bool SummaryView {
                get {
                    // Get the setting from the ViewState property bag, defaulting
                    // to true.
                    Object o = ViewState["SummaryView"];
                    return (o != null) ? (bool)o : true;
                }

                set {
                    // Save the setting in the ViewState property bag.
                    ViewState["SummaryView"] = value;

                    // Notify each child pane of the switched mode.
                    foreach (IContentsPane pane in Panes) {
                        pane.OnSetSummaryMode();
                    }
                }
            }

            //*********************************************************************
            //
            // ContentsPanel.Render Method
            //
            // Called by the framework to render the control. The behavior differs
            // depending on whether Summary or Details view is showing.
            //
            //*********************************************************************

            protected override void Render(HtmlTextWriter writer) {
                if (SummaryView) {
                    // Render all panes in Summary view.
                    RenderChildren(writer);
                }
                else {
                    // Render only the active pane in Details view.
                    ((Control)ActivePane).RenderControl(writer);
                }
            }

            //*********************************************************************
            //
            // ContentsPanel.OnBubbleEvent Method
            //
            // Called by the framework when postback events are bubbled up
            // from a child control. If the event source uses the special
            // command names listed above, this method automatically responds
            // to the event to change modes. This allows the developer to
            // provide UI for showing item details by simply placing a
            // control with the appropriate command name in a child pane.
            //
            //*********************************************************************

            protected override bool OnBubbleEvent(Object sender, EventArgs e) {
                bool handled = false;
                System.Web.UI.WebControls.CommandEventArgs commandArgs = e as System.Web.UI.WebControls.CommandEventArgs;
                if (commandArgs != null && commandArgs.CommandName != null) {
                    String commandName = commandArgs.CommandName.ToLower();

                    // Look for recognized command names.

                    if (commandName == DetailsCommand) {
                        // To show details, first find the child pane in which the
                        // event source is located.
                        Control ctl = (Control)sender;
                        while (ctl != null && ctl != this) {
                            IPanelPane pane = ctl as IPanelPane;
                            if (pane != null) {
                                // Make the pane active, and switch into Details view.
                                ActivePane = pane;
                                SummaryView = false;
                                handled = true;
                                break;
                            }
                            ctl = ctl.Parent;
                        }
                    }
                    else if (commandName == SummaryCommand) {
                        // Switch into Summary view.
                        SummaryView = true;
                        handled = true;
                    }
                }
                return handled;
            }

            //*********************************************************************
            //
            // ContentsPanel.ShowDetails Method
            //
            // The ShowDetails method switches the control into Details view,
            // and makes the specified child pane active. Child panes can
            // call this method to activate themselves.
            //
            //*********************************************************************

            public void ShowDetails(IPanelPane pane) {
                SummaryView = false;
                ActivePane = pane;
            }
        }

        //*********************************************************************
        //
        // HtmlTabbedPanelAdapter Class
        //
        // The HtmlTabbedPanelAdapter provides rendering for the TabbedPanel
        // class on devices that support HTML and JScript.
        //
        //*********************************************************************

        public class HtmlTabbedPanelAdapter : HtmlControlAdapter {
            //*********************************************************************
            //
            // HtmlTabbedPanelAdapter.Control Property
            //
            // Returns the attached control, strongly typed as a TabbedPanel.
            //
            //*********************************************************************

            protected new TabbedPanel Control {
                get {
                    return (TabbedPanel)base.Control;
                }
            }

            //*********************************************************************
            //
            // HtmlTabbedPanelAdapter.Render Method
            //
            // Renders the control. The TabbedPanel is rendered as one or more
            // rows of tabs that the user can click on to move between tabs.
            //
            //*********************************************************************

            public override void Render(HtmlMobileTextWriter writer) {
                IPanelPane activePane = Control.ActivePane;
                int tabsPerRow = Control.TabsPerRow;
                PanelPaneCollection panes = Control.Panes;
                int paneCount = panes.Count;

                // Figure out the number of visible panes.
                int[] visiblePanes = new int[paneCount];
                int visiblePaneCount = 0;
                for (int i = 0; i < paneCount; i++) {
                    if (((Control)panes[i]).Visible) {
                        visiblePanes[visiblePaneCount++] = i;
                    }
                }

                // Calculate how many rows are necessary.
                int rows = (visiblePaneCount + tabsPerRow - 1) / tabsPerRow;

                // make sure tabsPerRow doesn't exceed the number of visible panes
                tabsPerRow = (Control.TabsPerRow >  visiblePaneCount) ? visiblePaneCount : Control.TabsPerRow;

                // Open the table.
                writer.WriteBeginTag("table");
                writer.WriteAttribute("cellspacing", "0");
                writer.WriteAttribute("cellpadding", "2");
                writer.WriteAttribute("border", "0");
                writer.WriteLine(">");

                for (int row = rows - 1; row >= 0; row--) {
                    writer.WriteFullBeginTag("tr");
                    writer.WriteLine();
                    for (int col = 0; col < tabsPerRow; col++) {
                        writer.WriteBeginTag("td");
                        writer.WriteAttribute("width", "0");
                        writer.Write(">");
                        writer.WriteEndTag("td");

                        int i = row * tabsPerRow + col;
                        if (row > 0 && i >= visiblePaneCount) {
                            writer.WriteFullBeginTag("td");
                            writer.WriteEndTag("td");
                            continue;
                        }

                        int index = visiblePanes[i];
                        IPanelPane child = panes[index];
                        if (child == activePane) {
                            writer.WriteBeginTag("td");
                            writer.WriteAttribute("bgcolor", GetColorString(Control.ActiveTabColor, "#333333"));
                            writer.Write(">");

                            writer.WriteBeginTag("font");
                            writer.WriteAttribute("face", "Verdana");
                            writer.WriteAttribute("size", "-2");
                            writer.WriteAttribute("color", GetColorString(Control.ActiveTabTextColor, "#000000"));
                            writer.Write(">");

                            writer.WriteFullBeginTag("b");
                            writer.Write(" ");
                            writer.WriteText(child.Title, true);
                            writer.Write(" ");
                            writer.WriteEndTag("b");

                            writer.WriteEndTag("font");

                            writer.WriteEndTag("td");
                            writer.WriteLine();
                        }
                        else {
                            writer.WriteBeginTag("td");
                            writer.WriteAttribute("bgcolor", GetColorString(Control.TabColor, "#cccccc"));
                            writer.Write(">");

                            writer.WriteBeginTag("font");
                            writer.WriteAttribute("face", "Verdana");
                            writer.WriteAttribute("size", "-2");
                            writer.WriteAttribute("color", GetColorString(Control.TabTextColor, "#000000"));
                            writer.Write(">");

                            writer.Write(" ");
                            writer.WriteBeginTag("a");
                            RenderPostBackEventAsAttribute(writer, "href", index.ToString());
                            writer.Write(">");
                            writer.WriteText(child.Title, true);
                            writer.WriteEndTag("a");
                            writer.Write(" ");

                            writer.WriteEndTag("font");

                            writer.WriteEndTag("td");
                            writer.WriteLine();
                        }
                    }
                    writer.WriteEndTag("tr");
                    writer.WriteLine();

                    if (row > 0) {
                        writer.WriteFullBeginTag("tr");
                        writer.WriteBeginTag("td");
                        writer.WriteAttribute("height", "1");
                        writer.Write(">");
                        writer.WriteEndTag("td");
                        writer.WriteEndTag("tr");
                        writer.WriteLine();
                    }
                }

                writer.WriteEndTag("table");
                writer.WriteLine();

                writer.WriteBeginTag("table");
                writer.WriteAttribute("width", "100%");
                writer.WriteAttribute("height", "2");
                writer.WriteAttribute("border", "0");
                writer.WriteAttribute("cellspacing", "0");
                writer.WriteAttribute("bgcolor", "#000000");
                writer.Write(">");
                writer.WriteFullBeginTag("tr");
                writer.WriteFullBeginTag("td");
                writer.WriteEndTag("td");
                writer.WriteEndTag("tr");
                writer.WriteEndTag("table");
                writer.WriteBreak();

                ((Control)activePane).RenderControl(writer);
            }

            private static String GetColorString(Color color, String defaultColor) {
                return color != Color.Empty ? ColorTranslator.ToHtml(color) : defaultColor;
            }
        }

        public class WmlTabbedPanelAdapter : WmlControlAdapter {
            private List _menu;

            protected new TabbedPanel Control {
                get {
                    return (TabbedPanel)base.Control;
                }
            }

            public override void OnInit(EventArgs e) {
                _menu = new List();
                _menu.ItemCommand += new ListCommandEventHandler(OnListItemCommand);
                Control.Controls.AddAt(0, _menu);
            }

            public override void OnLoad(EventArgs e) {
                _menu.Items.Clear();
                int index = 0;
                foreach (IPanelPane child in Control.Panes) {
                    if (((Control)child).Visible) {
                        _menu.Items.Add(new MobileListItem(child.Title, index.ToString()));
                    }
                    index++;
                }
            }

            public override void Render(WmlMobileTextWriter writer) {
                Style st = new Style();
                st.Wrapping = (Wrapping)Style[Style.WrappingKey, true];
                st.Alignment = (Alignment)Style[Style.AlignmentKey, true];
                writer.EnterLayout(st);
                if (_menu.Visible) {
                    _menu.RenderControl(writer);
                }
                else {
                    ((Control)Control.ActivePane).RenderControl(writer);
                }

                writer.ExitLayout(st);
            }

            private void OnListItemCommand(Object sender, ListCommandEventArgs e) {
                _menu.Visible = false;
                Control.RaisePostBackEvent(e.ListItem.Value);
            }
        }

        public class ChtmlTabbedPanelAdapter : HtmlControlAdapter {
            protected new TabbedPanel Control {
                get {
                    return (TabbedPanel)base.Control;
                }
            }

            public override void Render(HtmlMobileTextWriter writer) {
                writer.EnterStyle(Style);

                IPanelPane activePane = Control.ActivePane;
                writer.Write("[ ");
                int index = 0;
                foreach (IPanelPane child in Control.Controls) {
                    if (!((Control)child).Visible) {
                        index++;
                        continue;
                    }
                    if (index > 0) {
                        writer.Write(" | ");
                    }

                    if (child == activePane) {
                        writer.Write("");
                        writer.WriteText(child.Title, true);
                        writer.Write("");
                    }
                    else {
                        writer.WriteBeginTag("a");
                        RenderPostBackEventAsAttribute(writer, "href", index.ToString());
                        writer.Write(">");
                        writer.WriteText(child.Title, true);
                        writer.WriteEndTag("a");
                    }

                    index++;
                }
                writer.Write(" ]");
                writer.WriteBreak();
                ((Control)activePane).RenderControl(writer);

                writer.ExitStyle(Style);
            }
        }
    }