File: UI\WebControls\MenuItem.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="MenuItem.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI.WebControls {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Web.UI;
    using System.Web.Util;
 
 
    /// <devdoc>
    ///     Provides a hierarchical menu item for use in the Menu class
    /// </devdoc>
    [ParseChildren(true, "ChildItems")]
    public sealed class MenuItem : IStateManager, ICloneable {
        
        private static readonly Unit HorizontalDefaultSpacing = Unit.Pixel(3);
 
        private bool _isTrackingViewState;
        private StateBag _viewState;
 
        private MenuItemCollection _childItems;
        private Menu _owner;
        private MenuItem _parent;
 
        private int _selectDesired;
 
        private object _dataItem;
        private MenuItemTemplateContainer _container;
 
        private int _index;
        internal string _id = string.Empty;
 
        private string _valuePath;
        private string _internalValuePath;
        private int _depth = -2;
 
        private bool _isRoot;
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem without a text or value
        /// </devdoc>
        public MenuItem() {
            _selectDesired = 0;
        }
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem with the specified owner Menu
        /// </devdoc>
        internal MenuItem(Menu owner, bool isRoot)
            : this() {
            _owner = owner;
            _isRoot = isRoot;
        }
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem with the specified text
        /// </devdoc>
        public MenuItem(string text)
            : this(text, null, null, null, null) {
        }
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem with the specified text, and value
        /// </devdoc>
        public MenuItem(string text, string value)
            : this(text, value, null, null, null) {
        }
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem with the specified text, value, and image URL
        /// </devdoc>
        public MenuItem(string text, string value, string imageUrl)
            : this(text, value, imageUrl, null, null) {
        }
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem with the specified text, value, image URL and navigateUrl
        /// </devdoc>
        public MenuItem(string text, string value, string imageUrl, string navigateUrl)
            : this(text, value, imageUrl, navigateUrl, null) {
        }
 
 
        /// <devdoc>
        ///     Constructs a new MenuItem with the specified text, value, image URL, navigation URL, and target.
        /// </devdoc>
        public MenuItem(string text, string value, string imageUrl, string navigateUrl, string target)
            : this() {
            if (text != null) {
                Text = text;
            }
 
            if (value != null) {
                Value = value;
            }
 
            if (!String.IsNullOrEmpty(imageUrl)) {
                ImageUrl = imageUrl;
            }
 
            if (!String.IsNullOrEmpty(navigateUrl)) {
                NavigateUrl = navigateUrl;
            }
 
            if (!String.IsNullOrEmpty(target)) {
                Target = target;
            }
        }
 
 
        /// <devdoc>
        ///     Gets the collection of children items parented to this MenuItem
        /// </devdoc>
        [Browsable(false)]
        [MergableProperty(false)]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public MenuItemCollection ChildItems {
            get {
                if (_childItems == null) {
                    _childItems = new MenuItemCollection(this);
                }
                return _childItems;
            }
        }
 
        internal MenuItemTemplateContainer Container {
            get {
                return _container;
            }
            set {
                _container = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets whether this item was created through databinding
        /// </devdoc>
        [Browsable(false)]
        [DefaultValue(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool DataBound {
            get {
                object o = ViewState["DataBound"];
                if (o == null) {
                    return false;
                }
                return (bool)o;
            }
        }
 
 
        /// <devdoc>
        ///     Gets path to the data to which this item is bound.
        /// </devdoc>
        [Browsable(false)]
        [DefaultValue("")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string DataPath {
            get {
                object s = ViewState["DataPath"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
        }
 
 
        /// <devdoc>
        ///     Gets the depth of the menu item.
        /// </devdoc>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int Depth {
            get {
                // -2 means not set yet, -1 means root
                if (_depth == -2) {
                    if (_isRoot) {
                        return -1;
                    }
 
                    if (Parent != null) {
                        _depth = Parent.Depth + 1;
                    }
                    else {
                        return 0;
                    }
                }
                return _depth;
            }
        }
 
 
        /// <devdoc>
        ///     Gets the data item for the menu item.
        /// </devdoc>
        [Browsable(false)]
        [DefaultValue(null)]
        public object DataItem {
            get {
                return _dataItem;
            }
        }
 
        [Browsable(true)]
        [DefaultValue(true)]
        [WebSysDescription(SR.MenuItem_Enabled)]
        public bool Enabled {
            get {
                object o = ViewState["Enabled"];
                return (o == null ? true : (bool)o);
            }
            set {
                ViewState["Enabled"] = value;
            }
        }
 
        internal string FormattedText {
            get {
                if (_owner.StaticItemFormatString.Length > 0 && Depth < _owner.StaticDisplayLevels) {
                    return String.Format(CultureInfo.CurrentCulture, _owner.StaticItemFormatString, Text);
                }
                else if (_owner.DynamicItemFormatString.Length > 0 && Depth >= _owner.StaticDisplayLevels) {
                    return String.Format(CultureInfo.CurrentCulture, _owner.DynamicItemFormatString, Text);
                }
                else {
                    return Text;
                }
            }
        }
 
        internal string Id {
            get {
                if (_id.Length == 0) {
                    Index = _owner.CreateItemIndex();
                    _id = _owner.ClientID + 'n' + Index;
                }
                return _id;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the image URl to be rendered for this item
        /// </devdoc>
        [DefaultValue("")]
        [Editor("System.Web.UI.Design.ImageUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor))]
        [UrlProperty()]
        [WebSysDescription(SR.MenuItem_ImageUrl)]
        public string ImageUrl {
            get {
                object s = ViewState["ImageUrl"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
            set {
                ViewState["ImageUrl"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the unique index for the menu item
        /// </devdoc>
        internal int Index {
            get {
                return _index;
            }
            set {
                _index = value;
            }
        }
 
        internal string InternalValuePath {
            get {
                if (_internalValuePath != null) {
                    return _internalValuePath;
                }
                if (_parent != null) {
                    // StringBuilder.Insert is expensive, but we need to build starting from the end.
                    // First build a list, then build the string starting from the end of the list.
                    List<string> pathParts = new List<string>();
                    pathParts.Add(TreeView.Escape(Value));
                    MenuItem parent = _parent;
                    while ((parent != null) && !parent._isRoot) {
                        if (parent._internalValuePath != null) {
                            pathParts.Add(parent._internalValuePath);
                            break;
                        }
                        else {
                            pathParts.Add(TreeView.Escape(parent.Value));
                        }
                        parent = parent._parent;
                    }
                    pathParts.Reverse();
                    _internalValuePath = String.Join(TreeView.InternalPathSeparator.ToString(), pathParts.ToArray());
                    return _internalValuePath;
                }
                else {
                    return String.Empty;
                }
            }
        }
 
        internal bool IsEnabled {
            get {
                return IsEnabledNoOwner && Owner.IsEnabled;
            }
        }
 
        internal bool IsEnabledNoOwner {
            get {
                MenuItem current = this;
                while (current != null) {
                    if (!current.Enabled) {
                        return false;
                    }
 
                    current = current.Parent;
                }
 
                return true;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the URL to navigate to when the item is clicked
        /// </devdoc>
        [DefaultValue("")]
        [Editor("System.Web.UI.Design.UrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor))]
        [UrlProperty()]
        [WebSysDescription(SR.MenuItem_NavigateUrl)]
        public string NavigateUrl {
            get {
                object s = ViewState["NavigateUrl"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
            set {
                ViewState["NavigateUrl"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets the owner Menu for this MenuItem, if there is one
        /// </devdoc>
        internal Menu Owner {
            get {
                return _owner;
            }
        }
 
 
        /// <devdoc>
        ///     Gets the parent MenuItem
        /// </devdoc>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MenuItem Parent {
            get {
                if ((_parent == null) || _parent._isRoot) {
                    return null;
                }
 
                return _parent;
            }
 
        }
 
 
        /// <devdoc>
        ///     Gets and sets the image URl to be rendered as a pop-out icon for this item if it has children
        /// </devdoc>
        [DefaultValue("")]
        [Editor("System.Web.UI.Design.ImageUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor))]
        [UrlProperty()]
        [WebSysDescription(SR.MenuItem_PopOutImageUrl)]
        public string PopOutImageUrl {
            get {
                object s = ViewState["PopOutImageUrl"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
            set {
                ViewState["PopOutImageUrl"] = value;
            }
        }
 
        [Browsable(true)]
        [DefaultValue(true)]
        [WebSysDescription(SR.MenuItem_Selectable)]
        public bool Selectable {
            get {
                object o = ViewState["Selectable"];
                return (o == null ? true : (bool)o);
            }
            set {
                ViewState["Selectable"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the selected state
        /// </devdoc>
        [Browsable(true)]
        [DefaultValue(false)]
        [WebSysDescription(SR.MenuItem_Selected)]
        public bool Selected {
            get {
                object o = ViewState["Selected"];
                if (o == null) {
                    return false;
                }
                return (bool)o;
            }
            set {
                SetSelected(value);
                NotifyOwnerSelected();
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the image URl to be rendered as a separator for this item
        /// </devdoc>
        [DefaultValue("")]
        [Editor("System.Web.UI.Design.ImageUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor))]
        [UrlProperty()]
        [WebSysDescription(SR.MenuItem_SeparatorImageUrl)]
        public string SeparatorImageUrl {
            get {
                object s = ViewState["SeparatorImageUrl"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
            set {
                ViewState["SeparatorImageUrl"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the target window that the MenuItem will browse to if selected
        /// </devdoc>
        [DefaultValue("")]
        [WebSysDescription(SR.MenuItem_Target)]
        public string Target {
            get {
                object s = ViewState["Target"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
            set {
                ViewState["Target"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the display text
        /// </devdoc>
        [DefaultValue("")]
        [Localizable(true)]
        [WebSysDescription(SR.MenuItem_Text)]
        public string Text {
            get {
                object s = ViewState["Text"];
                if (s == null) {
                    s = ViewState["Value"];
                    if (s == null) {
                        return String.Empty;
                    }
                }
                return (string)s;
            }
            set {
                ViewState["Text"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the MenuItem tooltip
        /// </devdoc>
        [DefaultValue("")]
        [Localizable(true)]
        [WebSysDescription(SR.MenuItem_ToolTip)]
        public string ToolTip {
            get {
                object s = ViewState["ToolTip"];
                if (s == null) {
                    return String.Empty;
                }
                return (string)s;
            }
            set {
                ViewState["ToolTip"] = value;
            }
        }
 
 
        /// <devdoc>
        ///     Gets and sets the value
        /// </devdoc>
        [DefaultValue("")]
        [Localizable(true)]
        [WebSysDescription(SR.MenuItem_Value)]
        public string Value {
            get {
                object s = ViewState["Value"];
                if (s == null) {
                    s = ViewState["Text"];
                    if (s == null) {
                        return String.Empty;
                    }
                }
                return (string)s;
            }
            set {
                ViewState["Value"] = value;
                ResetValuePathRecursive();
            }
        }
 
 
        /// <devdoc>
        ///     Gets the full path of the MenuItem
        /// </devdoc>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string ValuePath {
            get {
                if (_valuePath != null) {
                    return _valuePath;
                }
 
                if (_parent != null) {
                    string parentPath = _parent.ValuePath;
                    _valuePath = ((parentPath.Length == 0) && (_parent.Depth == -1)) ?
                        Value : parentPath + _owner.PathSeparator + Value;
                    return _valuePath;
                }
                else {
                    return String.Empty;
                }
            }
        }
 
 
        /// <devdoc>
        ///     The state for this MenuItem
        /// </devdoc>
        private StateBag ViewState {
            get {
                if (_viewState == null) {
                    _viewState = new StateBag();
                    if (_isTrackingViewState) {
                        ((IStateManager)_viewState).TrackViewState();
                    }
                }
                return _viewState;
            }
        }
 
        internal string GetExpandImageUrl() {
            if (ChildItems.Count > 0) {
                if (PopOutImageUrl.Length != 0) {
                    return _owner.ResolveClientUrl(PopOutImageUrl);
                }
                else {
                    if (Depth < _owner.StaticDisplayLevels) {
                        if (_owner.StaticPopOutImageUrl.Length != 0) {
                            return _owner.ResolveClientUrl(_owner.StaticPopOutImageUrl);
                        }
                        else if (_owner.StaticEnableDefaultPopOutImage) {
                            return _owner.GetImageUrl(Menu.PopOutImageIndex);
                        }
                    }
                    else {
                        if (_owner.DynamicPopOutImageUrl.Length != 0) {
                            return _owner.ResolveClientUrl(_owner.DynamicPopOutImageUrl);
                        }
                        else if (_owner.DynamicEnableDefaultPopOutImage) {
                            return _owner.GetImageUrl(Menu.PopOutImageIndex);
                        }
                    }
                }
            }
            return String.Empty;
        }
 
        internal bool NotTemplated() {
            return ((_owner.StaticItemTemplate == null || Depth >= _owner.StaticDisplayLevels) &&
                    (_owner.DynamicItemTemplate == null || Depth < _owner.StaticDisplayLevels));
        }
 
        private void NotifyOwnerSelected() {
            object o = ViewState["Selected"];
            bool value = (o == null ? false : (bool)o);
            // If the owner hasn't been set, remember that we want to select this item
            // when the owner is determined
            if (_owner == null) {
                _selectDesired = (value ? +1 : -1);
                return;
            }
            else if (value) {
                // Set the Menu's selected item to this one
                _owner.SetSelectedItem(this);
            }
            else if (this == _owner.SelectedItem) {
                _owner.SetSelectedItem(null);
            }
        }
 
 
        /// <devdoc>
        ///     Renders the contents of the item and its children.
        /// </devdoc>
        internal void Render(HtmlTextWriter writer, bool enabled, bool staticOnly) {
            Render(writer, enabled, staticOnly, true);
        }
 
 
        /// <devdoc>
        ///     Renders the contents of the item and its children.
        /// </devdoc>
        internal void Render(HtmlTextWriter writer, bool enabled, bool staticOnly, bool recursive) {
            enabled = enabled && Enabled;
            // If children exist, maybe render them
            int nextDepth = Depth + 1;
            if (ChildItems.Count > 0 && nextDepth < _owner.MaximumDepth) {
                // <table cellpadding="0" cellspacing="0" border="0">
                // Find the right style:
                SubMenuStyle subMenuStyle = _owner.GetSubMenuStyle(this);
                string styleClass = null;
                if (_owner.Page != null && _owner.Page.SupportsStyleSheets) {
                    styleClass = _owner.GetSubMenuCssClassName(this);
                }
 
                if (nextDepth >= _owner.StaticDisplayLevels) {
                    // The submenu is dynamic
                    if (!staticOnly && enabled && !(_owner.DesignMode && recursive)) {
                        // Not recreating a panel each time: panel is created and configured only once from Menu.Panel.
                        PopOutPanel panel = _owner.Panel;
                        if (_owner.Page != null && _owner.Page.SupportsStyleSheets) {
                            panel.ScrollerClass = _owner.GetCssClassName(ChildItems[0], false);
                            panel.ScrollerStyle = null;
                        }
                        else {
                            panel.ScrollerClass = null;
                            panel.ScrollerStyle = _owner.GetMenuItemStyle(ChildItems[0]);
                        }
                        if (_owner.Page != null && _owner.Page.SupportsStyleSheets) {
                            panel.CssClass = styleClass;
                            panel.SetInternalStyle(null);
                        }
                        else if (!subMenuStyle.IsEmpty) {
                            panel.CssClass = String.Empty;
                            panel.SetInternalStyle(subMenuStyle);
                        }
                        else {
                            panel.CssClass = String.Empty;
                            panel.SetInternalStyle(null);
                            panel.BackColor = Color.Empty;
                        }
                        panel.ID = Id + "Items";
                        panel.RenderBeginTag(writer);
                        writer.AddAttribute(HtmlTextWriterAttribute.Border, "0");
                        writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");
                        writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
                        writer.RenderBeginTag(HtmlTextWriterTag.Table);
                        for (int i = 0; i < ChildItems.Count; i++) {
                            ChildItems[i].RenderItem(writer, i, enabled, Orientation.Vertical);
                        }
                        writer.RenderEndTag(); // Table
                        panel.RenderEndTag(writer);
 
                        if (recursive) {
                            for (int i = 0; i < ChildItems.Count; i++) {
                                ChildItems[i].Render(writer, enabled, false);
                            }
                        }
                    }
                }
                else {
                    // The submenu is static
                    writer.AddAttribute(HtmlTextWriterAttribute.Border, "0");
                    writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");
                    writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
                    writer.AddAttribute(HtmlTextWriterAttribute.Width, "100%");
                    if (_owner.Page != null && _owner.Page.SupportsStyleSheets) {
                        if (styleClass != null && styleClass.Length > 0) {
                            writer.AddAttribute(HtmlTextWriterAttribute.Class, styleClass);
                        }
                    }
                    else {
                        subMenuStyle.AddAttributesToRender(writer);
                    }
                    writer.RenderBeginTag(HtmlTextWriterTag.Table);
                    if (_owner.Orientation == Orientation.Horizontal) {
                        // Render one global tr as items won't render any
                        writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                    }
                    bool isNextStatic = (nextDepth + 1 < _owner.StaticDisplayLevels);
                    bool isNextInRange = (nextDepth + 1 < _owner.MaximumDepth);
                    for (int i = 0; i < ChildItems.Count; i++) {
                        if (recursive
                            && ChildItems[i].ChildItems.Count != 0
                            && ((enabled && ChildItems[i].Enabled) || isNextStatic)
                            && isNextInRange) {
 
                            // If the next items are dynamic, we want to render the div inside the item's
                            // td so that we don't generate a tr that contains only absolute positioned
                            // divs, which would cause a gap to appear (VSWhidbey 354884)
                            if (isNextStatic) {
                                ChildItems[i].RenderItem(writer, i, enabled, _owner.Orientation);
                                if (_owner.Orientation == Orientation.Vertical) {
                                    writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                                    writer.RenderBeginTag(HtmlTextWriterTag.Td);
                                    ChildItems[i].Render(writer, enabled, staticOnly);
                                    writer.RenderEndTag(); //td
                                    writer.RenderEndTag(); //tr
                                }
                                else {
                                    writer.RenderBeginTag(HtmlTextWriterTag.Td);
                                    ChildItems[i].Render(writer, enabled, staticOnly);
                                    writer.RenderEndTag(); //td
                                }
                            }
                            else {
                                ChildItems[i].RenderItem(writer, i, enabled, _owner.Orientation, staticOnly);
                            }
                        }
                        else {
                            ChildItems[i].RenderItem(writer, i, enabled, _owner.Orientation);
                        }
                    }
                    if (_owner.Orientation == Orientation.Horizontal) {
                        // Render global /tr
                        writer.RenderEndTag();
                    }
                    writer.RenderEndTag(); //table
                    if (!isNextStatic && !staticOnly && recursive && isNextInRange) {
                        for (int i = 0; i < ChildItems.Count; i++) {
                            if (ChildItems[i].ChildItems.Count != 0
                                && ((enabled && ChildItems[i].Enabled))) {
 
                                // The next items are dynamic, so we want to render the div outside the menu table
                                // so that we don't generate a tr that contains only absolute positioned
                                // divs, which would cause a gap to appear (VSWhidbey 354884)
                                ChildItems[i].Render(writer, enabled, false, true);
                            }
                        }
                    }
                }
            }
        }
 
 
        /// <devdoc>
        ///     Renders the contents of the item but not its children.
        /// </devdoc>
        internal void RenderItem(HtmlTextWriter writer, int position, bool enabled, Orientation orientation) {
            RenderItem(writer, position, enabled, orientation, false);
        }
 
        internal void RenderItem(HtmlTextWriter writer, int position, bool enabled, Orientation orientation, bool staticOnly) {
            enabled = enabled && Enabled;
            int depth = Depth;
            MenuItemStyle mergedStyle = _owner.GetMenuItemStyle(this);
 
            int depthPlusOne = Depth + 1;
 
            bool staticTopSeparator = (depth < _owner.StaticDisplayLevels) && (_owner.StaticTopSeparatorImageUrl.Length != 0);
            bool dynamicTopSeparator = (depth >= _owner.StaticDisplayLevels) && (_owner.DynamicTopSeparatorImageUrl.Length != 0);
            // The separator is in a separate td in the vertical case, in a separate tr otherwise.
            if (staticTopSeparator || dynamicTopSeparator) {
                if (orientation == Orientation.Vertical) {
                    writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                }
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                if (staticTopSeparator) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Src, _owner.ResolveClientUrl(_owner.StaticTopSeparatorImageUrl));
                }
                else {
                    writer.AddAttribute(HtmlTextWriterAttribute.Src, _owner.ResolveClientUrl(_owner.DynamicTopSeparatorImageUrl));
                }
                writer.AddAttribute(HtmlTextWriterAttribute.Alt, String.Empty);
                writer.RenderBeginTag(HtmlTextWriterTag.Img);
                writer.RenderEndTag(); // Img
                writer.RenderEndTag(); // Td
                if (orientation == Orientation.Vertical) {
                    writer.RenderEndTag(); // Tr
                }
            }
 
            // Top spacing
            if ((mergedStyle != null) && !mergedStyle.ItemSpacing.IsEmpty && ((depth != 0) || (position != 0))) {
                RenderItemSpacing(writer, mergedStyle.ItemSpacing, orientation);
            }
 
            if (!staticOnly && _owner.Enabled) {
                if (depthPlusOne > _owner.StaticDisplayLevels) {
                    // Only the last static level and dynamic levels need hover behavior.
                    // And then only if they are selectable or have children.
                    if ((Selectable && Enabled) || ChildItems.Count != 0) {
                        writer.AddAttribute("onmouseover", "Menu_HoverDynamic(this)");
                        RenderItemEvents(writer);
                    }
                    else {
                        // dynamic disabled or unselectable items without children still need to maintain the menu open
                        writer.AddAttribute("onmouseover", "Menu_HoverDisabled(this)");
                        writer.AddAttribute("onmouseout", "Menu_Unhover(this)");
                    }
                }
                else if (depthPlusOne == _owner.StaticDisplayLevels) {
                    // Here's for the last static level
                    if ((Selectable && Enabled) || ChildItems.Count != 0) {
                        writer.AddAttribute("onmouseover", "Menu_HoverStatic(this)");
                        RenderItemEvents(writer);
                    }
                }
                else if (Selectable && Enabled) {
                    // Other nodes need hover styles but no expand
                    writer.AddAttribute("onmouseover", "Menu_HoverRoot(this)");
                    RenderItemEvents(writer);
                }
            }
            // Tooltip
            if (ToolTip.Length != 0) {
                writer.AddAttribute(HtmlTextWriterAttribute.Title, ToolTip);
            }
            // Set the id
            writer.AddAttribute(HtmlTextWriterAttribute.Id, Id);
            if (orientation == Orientation.Vertical) {
                // <tr>
                writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            }
 
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
 
            // Set the style
            if (_owner.Page != null && _owner.Page.SupportsStyleSheets) {
                string styleClass = _owner.GetCssClassName(this, false);
                if (styleClass.Trim().Length > 0) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, styleClass);
                }
            }
            else if (mergedStyle != null) {
                mergedStyle.AddAttributesToRender(writer);
            }
 
            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");
            writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
            writer.AddAttribute(HtmlTextWriterAttribute.Border, "0");
            writer.AddAttribute(HtmlTextWriterAttribute.Width, "100%");
            writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            if (!_owner.ItemWrap) {
                writer.AddStyleAttribute(HtmlTextWriterStyle.WhiteSpace, "nowrap");
            }
            if (orientation == Orientation.Vertical) {
                writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%");
            }
 
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
 
            if (_owner.Page != null && _owner.Page.SupportsStyleSheets) {
                bool applyInlineBorder;
                string styleClass = _owner.GetCssClassName(this, true, out applyInlineBorder);
                if (styleClass.Trim().Length > 0) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, styleClass);
                    if (applyInlineBorder) {
                        // Add inline style to force the border to none to override any CssClass (VSWhidbey 336610)
                        writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "none");
                        // And an inline font-size of 1em to avoid squaring relative font sizes by applying them twice
                        writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "1em");
                    }
                }
            }
            else {
                if (mergedStyle != null) {
                    mergedStyle.HyperLinkStyle.AddAttributesToRender(writer);
                }
            }
 
            string accessKey = _owner.AccessKey;
            if (enabled && Selectable) {
                // If there is a navigation url on this item, set up the navigation stuff
                if (NavigateUrl.Length > 0) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Href, _owner.ResolveClientUrl(NavigateUrl));
 
                    // Use the MenuItem Target if it has one, the Menu's if it doesn't
                    string target = ViewState["Target"] as string;
                    if (target == null) {
                        target = _owner.Target;
                    }
 
                    if (target.Length > 0) {
                        writer.AddAttribute(HtmlTextWriterAttribute.Target, target);
                    }
                }
                // Otherwise, write out a postback that will select the item
                else {
                    writer.AddAttribute(HtmlTextWriterAttribute.Href,
                        _owner.Page.ClientScript.GetPostBackClientHyperlink(_owner, InternalValuePath, true, true));
                }
                // AccessKey
                if (!_owner.AccessKeyRendered && (accessKey.Length != 0)) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, accessKey, true);
                    _owner.AccessKeyRendered = true;
                }
 
            }
            else {
                // Span if disabled or not selectable
                if (!enabled) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "true");
                }
                else if ((ChildItems.Count != 0) && (depthPlusOne >= _owner.StaticDisplayLevels)) {
                    // For accessibility reasons, we want the a to have an href even if it's not selectable
                    // because we want it to be focusable if it has dynamic children.
                    writer.AddAttribute(HtmlTextWriterAttribute.Href, "#");
                    writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "text");
                    // AccessKey
                    if (!_owner.AccessKeyRendered && (accessKey.Length != 0)) {
                        writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, accessKey, true);
                        _owner.AccessKeyRendered = true;
                    }
                }
            }
            if (depth != 0 && depth < _owner.StaticDisplayLevels) {
                Unit indent = _owner.StaticSubMenuIndent;
 
                // In 4.0, the default value of Menu.StaticSubMenuIndent was changed from 16px to Unit.Empty,
                // since the table and list rendering modes need to have different effective default values.
                // To maintain back compat, the effective default value for table rendering is 16px.
                // Dev10 Bug 741543
                if (indent.IsEmpty) {
                    indent = Unit.Pixel(16);
                }
 
                if (indent.Value != 0) {
                    double indentValue = indent.Value * depth;
                    if (indentValue <  Unit.MaxValue) {
                        indent = new Unit(indentValue, indent.Type);
                    }
                    else {
                        indent = new Unit(Unit.MaxValue, indent.Type);
                    }
                    writer.AddStyleAttribute("margin-left", indent.ToString(CultureInfo.InvariantCulture));
                }
            }
            // <a href=href>
            // We're rendering an A tag in all cases so that the client script can always find the items by tag name
            writer.RenderBeginTag(HtmlTextWriterTag.A);
 
            // Render out the item icon, if it is set and if the item is not templated
            if (ImageUrl.Length > 0 && NotTemplated()) {
                // <img>
                writer.AddAttribute(HtmlTextWriterAttribute.Src, _owner.ResolveClientUrl(ImageUrl));
                writer.AddAttribute(HtmlTextWriterAttribute.Alt, ToolTip);
                writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "none");
                writer.AddStyleAttribute("vertical-align", "middle");
                writer.RenderBeginTag(HtmlTextWriterTag.Img);
                writer.RenderEndTag();
            }
 
            // Item text
            RenderText(writer);
 
            // </a> or </span>
            writer.RenderEndTag();
 
            bool shouldRenderExpandImage = ((depthPlusOne >= _owner.StaticDisplayLevels) &&
                (depthPlusOne < _owner.MaximumDepth));
            string expandImageUrl = (shouldRenderExpandImage ? GetExpandImageUrl() : String.Empty);
 
            // Render some space if horizontal
            bool needsSpace = false;
            if ((orientation == Orientation.Horizontal) &&
                (depth < _owner.StaticDisplayLevels) &&
                (!shouldRenderExpandImage || (expandImageUrl.Length == 0)) &&
                ((mergedStyle == null) || mergedStyle.ItemSpacing.IsEmpty)) {
 
 
                if (((Depth + 1) < _owner.StaticDisplayLevels) && (ChildItems.Count != 0)) {
                    // next level is static too and exists, so we're not the last static
                    needsSpace = true;
                }
                else {
                    // Static item with no static children.
                    // We need to check if there are any static items at any level after this one.
                    // This is done by checking if any ancestor is not last.
                    // This walk should be marginally executed as multiple static display levels
                    // don't make sense on a horizontal menu.
                    MenuItem parent = this;
                    while (parent != null) {
                        if (((parent.Parent == null) &&
                             (_owner.Items.Count != 0) &&
                             (parent != _owner.Items[_owner.Items.Count - 1])) ||
                            ((parent.Parent != null) &&
                             (parent.Parent.ChildItems.Count != 0) &&
                             (parent != parent.Parent.ChildItems[parent.Parent.ChildItems.Count - 1]))) {
 
                            needsSpace = true;
                            break;
                        }
                        parent = parent.Parent;
                    }
                }
            }
 
            // </td>
            writer.RenderEndTag();
 
            if (shouldRenderExpandImage && expandImageUrl.Length > 0) {
                // <td>
                writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "0");
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                // <img>
                writer.AddAttribute(HtmlTextWriterAttribute.Src, expandImageUrl);
                writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "none");
                writer.AddStyleAttribute(HtmlTextWriterStyle.VerticalAlign, "middle");
                if (depth < _owner.StaticDisplayLevels) {
 
                    writer.AddAttribute(HtmlTextWriterAttribute.Alt,
                                        String.Format(CultureInfo.CurrentCulture, _owner.StaticPopOutImageTextFormatString, Text));
                }
                else if (depth >= _owner.StaticDisplayLevels) {
 
                    writer.AddAttribute(HtmlTextWriterAttribute.Alt,
                                        String.Format(CultureInfo.CurrentCulture, _owner.DynamicPopOutImageTextFormatString, Text));
                }
                writer.RenderBeginTag(HtmlTextWriterTag.Img);
                writer.RenderEndTag();
                // </td>
                writer.RenderEndTag();
            }
 
            
            writer.RenderEndTag(); // </tr>
            writer.RenderEndTag(); // </table>
 
            writer.RenderEndTag(); // </td>
            if (orientation == Orientation.Vertical) {
                writer.RenderEndTag(); // </tr>
            }
            // Bottom (or right) item spacing
            // We do not render the spacing on the last static item or on the last item of a dynamic submenu
            if ((mergedStyle != null) && !mergedStyle.ItemSpacing.IsEmpty) {
                RenderItemSpacing(writer, mergedStyle.ItemSpacing, orientation);
            }
            else if (needsSpace) {
                RenderItemSpacing(writer, HorizontalDefaultSpacing, orientation);
            }
            // Bottom separator
            bool separator = (SeparatorImageUrl.Length != 0);
            bool staticBottomSeparator = (depth < _owner.StaticDisplayLevels) && (_owner.StaticBottomSeparatorImageUrl.Length != 0);
            bool dynamicBottomSeparator = (depth >= _owner.StaticDisplayLevels) && (_owner.DynamicBottomSeparatorImageUrl.Length != 0);
            if (separator || staticBottomSeparator || dynamicBottomSeparator) {
                if (orientation == Orientation.Vertical) {
                    writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                }
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                if (separator) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Src, _owner.ResolveClientUrl(SeparatorImageUrl));
                }
                else if (staticBottomSeparator) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Src, _owner.ResolveClientUrl(_owner.StaticBottomSeparatorImageUrl));
                }
                else {
                writer.AddAttribute(HtmlTextWriterAttribute.Src, _owner.ResolveClientUrl(_owner.DynamicBottomSeparatorImageUrl));
                }
                writer.AddAttribute(HtmlTextWriterAttribute.Alt, String.Empty);
                writer.RenderBeginTag(HtmlTextWriterTag.Img);
                writer.RenderEndTag(); // Img
                writer.RenderEndTag(); // Td
                if (orientation == Orientation.Vertical) {
                    writer.RenderEndTag(); // Tr
                }
            }
        }
 
        private void RenderItemEvents(HtmlTextWriter writer) {
            writer.AddAttribute("onmouseout", "Menu_Unhover(this)");
            if (_owner.IsNotIE) {
                writer.AddAttribute("onkeyup", "Menu_Key(event)");
            }
            else {
                writer.AddAttribute("onkeyup", "Menu_Key(this)");
            }
        }
 
        private void RenderItemSpacing(HtmlTextWriter writer, Unit spacing, Orientation orientation) {
            if (orientation == Orientation.Vertical) {
                writer.AddStyleAttribute(HtmlTextWriterStyle.Height,
                    spacing.ToString(CultureInfo.InvariantCulture));
                writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.RenderEndTag();
                writer.RenderEndTag();
            }
            else {
                writer.AddStyleAttribute(HtmlTextWriterStyle.Width,
                    spacing.ToString(CultureInfo.InvariantCulture));
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.RenderEndTag();
            }
        }
 
        internal void RenderText(HtmlTextWriter writer) {
            if (Container != null &&
                ((_owner.StaticItemTemplate != null && Depth < _owner.StaticDisplayLevels) ||
                (_owner.DynamicItemTemplate != null && Depth >= _owner.StaticDisplayLevels))) {
 
                Container.RenderControl(writer);
            }
            else {
                writer.Write(FormattedText);
            }
        }
 
        internal void ResetValuePathRecursive() {
            if (_valuePath != null) {
                _valuePath = null;
                foreach (MenuItem child in ChildItems) {
                    child.ResetValuePathRecursive();
                }
            }
        }
 
 
        /// <devdoc>
        ///     Marks this item as a databound item
        /// </devdoc>
        internal void SetDataBound(bool dataBound) {
            ViewState["DataBound"] = dataBound;
        }
 
 
        /// <devdoc>
        ///     Sets the data item for use by the user in databinding
        /// </devdoc>
        internal void SetDataItem(object dataItem) {
            _dataItem = dataItem;
        }
 
        /// <devdoc>
        ///     Sets the data path for use by the Menu in databinding
        /// </devdoc>
        internal void SetDataPath(string dataPath) {
            ViewState["DataPath"] = dataPath;
        }
 
        internal void SetDepth(int depth) {
            _depth = depth;
        }
 
        internal void SetDirty() {
            ViewState.SetDirty(true);
 
            if (ChildItems.Count > 0) {
                ChildItems.SetDirty();
            }
        }
 
 
        /// <devdoc>
        ///     Sets the owner Menu of this item.
        /// </devdoc>
        internal void SetOwner(Menu owner) {
            _owner = owner;
 
            if (_selectDesired == +1) {
                _selectDesired = 0;
                Selected = true;
            }
            else if (_selectDesired == -1) {
                _selectDesired = 0;
                Selected = false;
            }
 
            foreach (MenuItem item in ChildItems) {
                item.SetOwner(_owner);
            }
        }
 
 
        /// <devdoc>
        ///     Sets the parent MenuItem of the item
        /// </devdoc>
        internal void SetParent(MenuItem parent) {
            _parent = parent;
            SetPath(null);
        }
 
        internal void SetPath(string newPath) {
            _internalValuePath = newPath;
            _depth = -2;
        }
 
        internal void SetSelected(bool value) {
            ViewState["Selected"] = value;
 
            // If the owner hasn't been set, remember that we want to select this node
            // when the owner is determined
            if (_owner == null) {
                _selectDesired = (value ? +1 : -1);
            }
        }
 
            #region IStateManager implementation
 
        /// <internalonly/>
        bool IStateManager.IsTrackingViewState {
            get {
                return _isTrackingViewState;
            }
        }
 
 
        /// <internalonly/>
        void IStateManager.LoadViewState(object state) {
            object[] itemState = (object[])state;
 
            if (itemState != null) {
                if (itemState[0] != null) {
                    ((IStateManager)ViewState).LoadViewState(itemState[0]);
                }
                // We need to call the selected setter so that the owner can be notified
                // N.B. The treeview does not need to do that
                // because it's loading the state of its node differently because of the richer client-side behavior.
                NotifyOwnerSelected();
 
                if (itemState[1] != null) {
                    ((IStateManager)ChildItems).LoadViewState(itemState[1]);
                }
            }
        }
 
 
        /// <internalonly/>
        object IStateManager.SaveViewState() {
            object[] state = new object[2];
            if (_viewState != null) {
                state[0] = ((IStateManager)_viewState).SaveViewState();
            }
 
            if (_childItems != null) {
                state[1] = ((IStateManager)_childItems).SaveViewState();
            }
 
            if ((state[0] == null) && (state[1] == null)) {
                return null;
            }
 
            return state;
        }
 
 
        /// <internalonly/>
        void IStateManager.TrackViewState() {
            _isTrackingViewState = true;
 
            if (_viewState != null) {
                ((IStateManager)_viewState).TrackViewState();
            }
 
            if (_childItems != null) {
                ((IStateManager)_childItems).TrackViewState();
            }
        }
            #endregion
 
            #region ICloneable implementation
 
        /// <internalonly/>
        object ICloneable.Clone() {
            MenuItem newItem = new MenuItem();
            newItem.Enabled = Enabled;
            newItem.ImageUrl = ImageUrl;
            newItem.NavigateUrl = NavigateUrl;
            newItem.PopOutImageUrl = PopOutImageUrl;
            newItem.Selectable = Selectable;
            newItem.Selected = Selected;
            newItem.SeparatorImageUrl = SeparatorImageUrl;
            newItem.Target = Target;
            newItem.Text = Text;
            newItem.ToolTip = ToolTip;
            newItem.Value = Value;
 
            return newItem;
        }
            #endregion
    }
 
    public sealed class MenuItemTemplateContainer : Control, IDataItemContainer {
 
        private int _itemIndex;
        private object _dataItem;
 
        public MenuItemTemplateContainer(int itemIndex, MenuItem dataItem) {
            _itemIndex = itemIndex;
            _dataItem = dataItem;
        }
 
        public object DataItem {
            get {
                return _dataItem;
            }
            set {
                _dataItem = value;
            }
        }
 
        public int ItemIndex {
            get {
                return _itemIndex;
            }
        }
 
        protected override bool OnBubbleEvent(object source, EventArgs e) {
            CommandEventArgs ce = e as CommandEventArgs;
            if (ce != null) {
                if (ce is MenuEventArgs) {
                    RaiseBubbleEvent(this, ce);
                }
                else {
                    MenuEventArgs args = new MenuEventArgs((MenuItem)_dataItem, source, ce);
                    RaiseBubbleEvent(this, args);
                }
                return true;
            }
            return false;
        }
 
        object IDataItemContainer.DataItem {
            get {
                return _dataItem;
            }
        }
 
        int IDataItemContainer.DataItemIndex {
            get {
                return ItemIndex;
            }
        }
 
        int IDataItemContainer.DisplayIndex {
            get {
                return ItemIndex;
            }
        }
    }
}