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 ASPNetPortal.MobileControls;

[ assembly:TagPrefix("ASPNetPortal.MobileControls", "portal") ]

namespace ASPNetPortal {

    //*********************************************************************
    //
    // 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 ASPNetPortal.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 IBuySpy Mobile Portal. 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);
        }
    }
}