File: UI\MobileControls\Design\MobileContainerDesigner.cs
Project: ndp\fx\src\mit\System\Web\System.Web.Mobile.csproj (System.Web.Mobile)
//------------------------------------------------------------------------------
// <copyright file="MobileContainerDesigner.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI.Design.MobileControls
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.Reflection;
    using System.Web.UI.Design.MobileControls.Adapters;
    using System.Web.UI.MobileControls;
    using System.Web.UI.MobileControls.Adapters;
 
    using IHTMLElement = NativeMethods.IHTMLElement;
    using IHTMLElementCollection = NativeMethods.IHTMLElementCollection;
 
    /// <summary>
    ///    <para>Provides a base designer class for all mobile container controls.</para>
    /// </summary>
    [
        System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand,
        Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
    ]
    [Obsolete("The System.Web.Mobile.dll assembly has been deprecated and should no longer be used. For information about how to develop ASP.NET mobile applications, see http://go.microsoft.com/fwlink/?LinkId=157231.")]
    internal abstract class MobileContainerDesigner : ControlDesigner, IMobileDesigner
    {
        private MobileControl               _mobileControl;
        private readonly Size               _defaultSize;
        private bool                        _containmentStatusDirty = true;
        private bool                        _hasAttributesCached = false;
        private bool                        _shouldDirtyPage = false;
        private ContainmentStatus           _containmentStatus = ContainmentStatus.Unknown;
        private IDictionary                 _behaviorAttributes;
        private String                      _currentErrorMessage = null;
        private IWebFormsDocumentService    _iWebFormsDocumentService;
        private IMobileWebFormServices      _iMobileWebFormServices;
        private EventHandler                _loadComplete = null;
 
        // cached Behavior object
        private IHtmlControlDesignerBehavior _cachedBehavior = null;
 
        /// <summary>
        ///    <para>
        ///       Initializes an instance of the <see cref='System.Web.UI.Design.MobileControls.MobileContainerDesigner'/> class.
        ///    </para>
        /// </summary>
        internal MobileContainerDesigner()
        {
            ReadOnly = false;
 
            _defaultSize = new Size(300, 100);
            _behaviorAttributes = new HybridDictionary();
        }
 
        /// <summary>
        /// return the containment status
        /// </summary>
        protected ContainmentStatus ContainmentStatus
        {
            get
            {
                if (!_containmentStatusDirty)
                {
                    return _containmentStatus;
                }
 
                _containmentStatus =
                    DesignerAdapterUtil.GetContainmentStatus(_mobileControl);
 
                _containmentStatusDirty = false;
                return _containmentStatus;
            }
        }
 
        internal Object DesignTimeElementInternal
        {
            get
            {
                return typeof(HtmlControlDesigner).InvokeMember("DesignTimeElement", 
                    BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic, 
                    null, this, null, CultureInfo.InvariantCulture);
            }
        }
 
        private IMobileWebFormServices IMobileWebFormServices
        {
            get
            {
                if (_iMobileWebFormServices == null)
                {
                    _iMobileWebFormServices =
                        (IMobileWebFormServices)GetService(typeof(IMobileWebFormServices));
                }
 
                return _iMobileWebFormServices;
            }
        }
 
        private IWebFormsDocumentService IWebFormsDocumentService
        {
            get
            {
                if (_iWebFormsDocumentService == null)
                {
                    _iWebFormsDocumentService =
                        (IWebFormsDocumentService)GetService(typeof(IWebFormsDocumentService));
 
                    Debug.Assert(_iWebFormsDocumentService != null);
                }
 
                return _iWebFormsDocumentService;
            }
        }
 
        /// <summary>
        ///     Indicates whether the initial page load is completed
        /// </summary>
        protected bool LoadComplete
        {
            get
            {
                return !IWebFormsDocumentService.IsLoading;
            }
        }
 
        /// <summary>
        ///    Control's style, available only when page is MobilePage
        /// </summary>
        protected Style Style
        {
            get
            {
                if (!DesignerAdapterUtil.InMobilePage(_mobileControl))
                {
                    return null;
                }
 
                Style style = ((ControlAdapter)_mobileControl.Adapter).Style;
 
                // Each MobileControl should have its own style
                Debug.Assert(style != null);
 
                return style;
            }
        }
 
        /// <summary>
        ///    Apply style related properties to behavior
        /// </summary>
        /// <param name="propName">
        ///    property that needs to be applied, null to apply all
        /// </param>
        private void ApplyPropertyToBehavior(String propName)
        {
            if (Style == null)
            {
                return;
            }
 
            if (propName == null || propName.Equals("BackColor"))
            {
                Color backColor = (Color)Style[Style.BackColorKey, true];
                SetBehaviorStyle("backgroundColor", ColorTranslator.ToHtml(backColor));
            }
            if (propName == null || propName.Equals("ForeColor"))
            {
                Color foreColor = (Color)Style[Style.ForeColorKey, true];
                SetBehaviorStyle("color", ColorTranslator.ToHtml(foreColor));
            }
            if (propName == null || propName.Equals("Font"))
            {
                bool bold =
                    (BooleanOption)Style[Style.BoldKey, true] == BooleanOption.True;
                bool italic =
                    (BooleanOption)Style[Style.ItalicKey, true] == BooleanOption.True;
                FontSize  fontSize  = (FontSize) Style[Style.FontSizeKey , true];
                String    fontName  = (String)   Style[Style.FontNameKey , true];
 
                SetBehaviorStyle("fontWeight", bold? "bold" : "normal");
                SetBehaviorStyle("fontStyle", italic? "italic" : "normal");
 
                if (fontSize == FontSize.Large)
                {
                    SetBehaviorStyle("fontSize", "medium");
                }
                else if (fontSize == FontSize.Small)
                {
                    SetBehaviorStyle("fontSize", "x-small");
                }
                else
                {
                    RemoveBehaviorStyle("fontSize");
                }
 
                SetBehaviorStyle("fontFamily", fontName);
            }
            if (propName == null || propName.Equals("Alignment"))
            {
                Alignment alignment = (Alignment)Style[Style.AlignmentKey, true];
                bool alignmentNotSet = alignment == Alignment.NotSet;
 
                SetBehaviorStyle("textAlign",
                    alignmentNotSet ? "" : Enum.Format(typeof(Alignment), alignment, "G"));
            }
        }
 
        /// <summary>
        ///   Performs the cleanup of the designer class.
        /// </summary>
        /// <seealso cref='IDesigner'/>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_loadComplete != null)
                {
                    IWebFormsDocumentService.LoadComplete -= _loadComplete;
                    _loadComplete = null;
                }
            }
 
            base.Dispose(disposing);
        }
 
        /// <summary>
        ///   The default size of Container Control.
        /// </summary>
        protected virtual Size GetDefaultSize()
        {
            return _defaultSize;
        }
 
        /// <summary>
        ///    non-null string will render the text as an image
        ///    on the top of container control.
        /// </summary>
        protected virtual String GetErrorMessage(out bool infoMode)
        {
            infoMode = false;
            return null;
        }
 
        /// <summary>
        ///    <para>
        ///       Initializes the designer using
        ///       the specified component.
        ///    </para>
        /// </summary>
        /// <param name='component'>
        ///    The control element being designed.
        /// </param>
        /// <remarks>
        ///    <para>
        ///       This is called by the designer host to establish the component being
        ///       designed.
        ///    </para>
        /// </remarks>
        /// <seealso cref='System.ComponentModel.Design.IDesigner'/>
        public override void Initialize(IComponent component)
        {
            Debug.Assert(component is MobileControl,
                         "MobileContainerDesigner.Initialize - Invalid Mobile Control");
 
            _mobileControl = (MobileControl) component;
            base.Initialize(component);
 
            _loadComplete = new EventHandler(this.OnLoadComplete);
            IWebFormsDocumentService.LoadComplete += _loadComplete;
        }
 
        /// <summary>
        ///    return true if the property is an appearance attribute that needs
        ///    to apply to all child controls.
        /// </summary>
        /// <param name="propertyName">
        /// </param>
        private bool IsAppearanceAttribute(String propertyName)
        {
            return (
                propertyName.Equals("Font") ||
                propertyName.Equals("ForeColor") ||
                propertyName.Equals("BackColor") ||
                propertyName.Equals("Wrapping") ||
                propertyName.Equals("Alignment") ||
                propertyName.Equals("StyleReference"));
        }
 
        internal virtual void OnBackgroundImageChange(String message, bool infoMode)
        {
        }
 
        /// <summary>
        ///    <para>
        ///       Notification that is called when the designer is attached to the behavior.
        ///    </para>
        /// </summary>
        protected override void OnBehaviorAttached()
        {
            Debug.Assert(_cachedBehavior == null);
            _cachedBehavior = Behavior;
 
            PrefixDeviceSpecificTags();
            base.OnBehaviorAttached();
 
            // Reload the original state if an old Behavior is cached.
            if (_hasAttributesCached)
            {
                ReloadBehaviorState();
            }
        }
 
        /// <summary>
        ///    Notification that is called when the behavior is detached from designer
        /// </summary>
        protected override void OnBehaviorDetaching()
        {
            // dispose the cached behavior.
            _cachedBehavior = null;
        }
 
        /// <summary>
        ///    <para>
        ///       Delegate to handle component changed event.
        ///    </para>
        /// </summary>
        /// <param name='sender'>
        ///    The object sending the event.
        /// </param>
        /// <param name='ce'>
        ///    The event object used when firing a component changed notification.
        /// </param>
        /// <remarks>
        ///    <para>
        ///       This is called after a property has been changed. It allows the implementor
        ///       to do any post-processing that may be needed after a property change.
        ///    </para>
        /// </remarks>
        public override void OnComponentChanged(Object sender, ComponentChangedEventArgs ce)
        {
            // Delegate to the base class implementation first!
            base.OnComponentChanged(sender, ce);
 
            MemberDescriptor member = ce.Member;
            if (member != null &&
                member.GetType().FullName.Equals(Constants.ReflectPropertyDescriptorTypeFullName))
            {
                PropertyDescriptor propDesc = (PropertyDescriptor)member;
                String propName = propDesc.Name;
 
                if (IsAppearanceAttribute(propName))
                {
                    // Update control rendering
                    UpdateRenderingRecursive();
                }
            }
        }
 
        /// <summary>
        ///   Subclasses can override to modify their container appearance,
        ///   this method is invoked by OnLoadComplete()
        /// </summary>
        protected virtual void OnContainmentChanged()
        {
            // do nothing
        }
 
        /// <summary>
        ///     helper method for external UIs
        /// </summary>
        protected virtual void OnInternalChange()
        {
            ISite site = _mobileControl.Site;
            if (site != null)
            {
                IComponentChangeService changeService =
                    (IComponentChangeService)site.GetService(typeof(IComponentChangeService));
                if (changeService != null)
                {
                    try
                    {
                        changeService.OnComponentChanging(_mobileControl, null);
                    }
                    catch (CheckoutException ex)
                    {
                        if (ex == CheckoutException.Canceled)
                            return;
                        throw;
                    }
                    changeService.OnComponentChanged(_mobileControl, null, null, null);
                }
            }
        }
 
        /// <summary>
        ///    <para>
        ///       Notification that is called when the page completes loading.
        ///    </para>
        /// </summary>
        private void OnLoadComplete(Object source, EventArgs e)
        {
            // Need to apply behavior attributes since none are cached
            if (!_hasAttributesCached)
            {
                SetControlDefaultAppearance();
 
                // Apply the style properties to Behavior
                ApplyPropertyToBehavior(null);
            }
 
            bool infoMode = false;
            String msg = GetErrorMessage(out infoMode);
            if (msg != _currentErrorMessage || !_hasAttributesCached)
            {
                OnBackgroundImageChange(msg, infoMode);
                _currentErrorMessage = msg;
            }
 
            // we could reload the attributes
            _hasAttributesCached = true;
 
            // Change containment related appearance
            OnContainmentChanged();
 
            // Don't forget the change children appearance,
            // this call is necessary to solve multi-nested control problem.
            UpdateRenderingRecursive();
 
            // Make the page dirty by calling OnInternalChange if an subsitution occurs.
            if (_shouldDirtyPage)
            {
                OnInternalChange();
                _shouldDirtyPage = false;
            }
        }
 
        /// <summary>
        ///    <para>
        ///       Notification that is called when the associated control is parented.
        ///    </para>
        /// </summary>
        public override void OnSetParent()
        {
            base.OnSetParent();
 
            // The containment status is invalidated
            _containmentStatusDirty = true;
 
            // Make sure the control refreshes when it is moved around
            if (LoadComplete)
            {
                OnLoadComplete(this, EventArgs.Empty);
            }
        }
 
        protected override void PreFilterProperties(IDictionary properties)
        {
            base.PreFilterProperties(properties);
 
            PropertyDescriptor property = (PropertyDescriptor) properties["Expressions"];
            if (property != null) {
                properties["Expressions"] = TypeDescriptor.CreateProperty(this.GetType(), property, BrowsableAttribute.No);
            }
        }
 
        /// <summary>
        ///    dynamically transform DeviceSpecific element to a server control,
        ///    called from OnBehaviorAttached
        /// </summary>
        private void PrefixDeviceSpecificTags()
        {
            IHTMLElement htmlElement = (IHTMLElement) DesignTimeElementInternal;
            Debug.Assert(htmlElement != null,
                "Invalid HTML element in FormDesigner.OnBehaviorAttached");
 
            IWebFormReferenceManager refMgr =
                (IWebFormReferenceManager) GetService(typeof(IWebFormReferenceManager));
            Debug.Assert(refMgr != null, "Did not get back IWebFormReferenceManager service.");
 
            String tagPrefix = refMgr.GetTagPrefix(typeof(DeviceSpecific));
            Debug.Assert(tagPrefix != null && tagPrefix.Length > 0, "TagPrefix is invalid");
 
            IHTMLElementCollection allChildren = (IHTMLElementCollection) htmlElement.GetChildren();
            if (null != allChildren)
            {
                bool substitutions = false;
                int nestingLevel = 0;
                String modifiedInnerHTML = String.Empty;
                for (Int32 i = 0; i < allChildren.GetLength(); i++)
                {
                    IHTMLElement htmlChild = (IHTMLElement) allChildren.Item(i, 0);
                    Debug.Assert(null != htmlChild, "htmlChild is null");
                    String childContent = htmlChild.GetOuterHTML();
                    String childUpperContent = childContent.ToUpper(CultureInfo.InvariantCulture);
                    if (childContent.StartsWith("<", StringComparison.Ordinal) &&
                        !(childContent.StartsWith("</", StringComparison.Ordinal) || (childContent.EndsWith("/>", StringComparison.Ordinal))))
                    {
                        if (!childUpperContent.StartsWith("<" + tagPrefix.ToUpper(CultureInfo.InvariantCulture) + ":", StringComparison.Ordinal))
                        {
                            nestingLevel++;
                        }
                    }
                    else if (childContent.StartsWith("</", StringComparison.Ordinal))
                    {
                        nestingLevel--;
                    }
                    if (1 == nestingLevel &&
                        childUpperContent.StartsWith("<DEVICESPECIFIC", StringComparison.Ordinal) &&
                        childUpperContent.EndsWith(">", StringComparison.Ordinal))
                    {
                        Debug.Assert(substitutions == false, "substitutions is true");
                        modifiedInnerHTML += "<" + tagPrefix + ":DeviceSpecific runat=\"server\">\r\n";
                        substitutions = true;
                    }
                    else if (1 == nestingLevel &&
                             childUpperContent.StartsWith("<DEVICESPECIFIC", StringComparison.Ordinal) &&
                             childUpperContent.EndsWith("/>", StringComparison.Ordinal))
                    {
                        modifiedInnerHTML += "<" + tagPrefix + ":DeviceSpecific runat=\"server\"></" + tagPrefix + ":DeviceSpecific>\r\n";
                        substitutions = true;
                    }
                    else if (0 == nestingLevel && 0 == String.Compare(childUpperContent, "</DEVICESPECIFIC>", StringComparison.Ordinal))
                    {
                        Debug.Assert(substitutions == true, "substitutions is false");
                        modifiedInnerHTML += "</" + tagPrefix + ":DeviceSpecific>\r\n";
                    }
                    else
                    {
                        modifiedInnerHTML += childContent + "\r\n";
                    }
                }
                if (substitutions)
                {
                    _shouldDirtyPage = true;
                    htmlElement.SetInnerHTML(modifiedInnerHTML);
                }
            }
        }
 
        /// <summary>
        ///    Reload the cached Behavior states
        /// </summary>
        private void ReloadBehaviorState()
        {
            Debug.Assert(Behavior != null && _behaviorAttributes != null);
 
            IDictionaryEnumerator enumerator = _behaviorAttributes.GetEnumerator();
            while (enumerator.MoveNext())
            {
                String key = (String)enumerator.Key;
                Object obj = _behaviorAttributes[key];
 
                Behavior.SetStyleAttribute(key, true, obj, true);
            }
        }
 
        /// <summary>
        ///    Remove the attribute from Behavior
        /// </summary>
        /// <param name="attribute">
        ///    attribute that need to be removed.
        /// </param>
        protected void RemoveBehaviorStyle(String attribute)
        {
            Debug.Assert (_behaviorAttributes != null);
 
            if (Behavior != null)
            {
                Behavior.RemoveStyleAttribute(attribute, true, true);
            }
 
            // also remove the cached attribute
            _behaviorAttributes.Remove(attribute);
        }
 
        /// <summary>
        ///    Apply the style attribute to Behavior
        /// </summary>
        /// <param name="attribute">
        ///    attribute that needs to be applied to Behavior
        /// </param>
        /// <param name="obj">
        ///    value to apply
        /// </param>
        protected void SetBehaviorStyle(String attribute, Object obj)
        {
            Debug.Assert (obj != null, "null object passed in!");
            Debug.Assert (_behaviorAttributes != null);
 
            // here we cache the value;
            // Note that the value is cached even if Behavior is not available,
            // this is because this method could be called between Behavior
            // detached and attached events, we want to re-apply these lost
            // attributes when Behavior is attached again.
            _behaviorAttributes[attribute] = obj;
 
            if (Behavior == null)
            {
                return;
            }
            Behavior.SetStyleAttribute(attribute, true, obj, true);
        }
 
        /// <summary>
        ///    This method will be called only once when the control is first created.
        /// </summary>
        protected virtual void SetControlDefaultAppearance()
        {
            // Default border appearance
            SetBehaviorStyle("borderWidth", "1px");
            SetBehaviorStyle("borderColor", ColorTranslator.ToHtml(SystemColors.ControlDark));
 
            // Default margin, paddings for container controls.
            SetBehaviorStyle("paddingTop", "8px");
            SetBehaviorStyle("paddingBottom", "8px");
            SetBehaviorStyle("paddingRight", "4px");
            SetBehaviorStyle("paddingLeft", "5px");
            SetBehaviorStyle("marginTop", "3px");
            SetBehaviorStyle("marginBottom", "3px");
            SetBehaviorStyle("marginRight", "5px");
            SetBehaviorStyle("marginLeft", "5px");
 
            // Setup background parameters
            SetBehaviorStyle("backgroundRepeat", "no-repeat");
            SetBehaviorStyle("backgroundAttachment", "fixed");
            SetBehaviorStyle("backgroundPositionX", "left");
            SetBehaviorStyle("backgroundPositionY", "top");
 
            // Container sze info.
            SetBehaviorStyle("height", GetDefaultSize().Height);
            SetBehaviorStyle("width", GetDefaultSize().Width);
        }
 
        /// <summary>
        ///   Update the designtime rendering for the container control
        /// </summary>
        public void UpdateRendering()
        {
            _mobileControl.RefreshStyle();
            ApplyPropertyToBehavior(null);
        }
 
        /// <summary>
        ///   Update the designtime rendering for the container control and all controls
        ///   inside this container control.
        /// </summary>
        private void UpdateRenderingRecursive()
        {
            UpdateRendering();
 
            if (IMobileWebFormServices != null)
            {
                IMobileWebFormServices.UpdateRenderingRecursive(_mobileControl);
            }
        }
    }
}