File: UI\MobileControls\Design\PropertyOverridesDialog.cs
Project: ndp\fx\src\mit\System\Web\System.Web.Mobile.csproj (System.Web.Mobile)
//------------------------------------------------------------------------------
// <copyright file="PropertyOverridesDialog.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.Globalization;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Reflection;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
 
    using Control = System.Web.UI.Control;
 
    using System.Web.UI.MobileControls;
    using System.Web.UI.Design.MobileControls.Converters;
    using System.Web.UI.Design.MobileControls.Util;
 
    [
        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 sealed class PropertyOverridesDialog :
        DesignerForm, IRefreshableDeviceSpecificEditor, IDeviceSpecificDesigner
    {
        private bool _isDirty = true;
        private IDeviceSpecificDesigner _designer;
        private int _mergingContext;
        private System.Windows.Forms.Control _header;
        private String _currentDeviceSpecificID;
        private IDictionary _cachedDeviceSpecifics =
            new HybridDictionary(true /* make case-insensitive */);
        private bool _ignoreSelectionChanged = true;
 
        private System.Windows.Forms.Label _lblProperties;
        private System.Windows.Forms.PropertyGrid _pgProperties;
        private System.Windows.Forms.Button _btnEditFilters;
        private System.Windows.Forms.ComboBox _cbChoices;
        private System.Windows.Forms.Label _lblAppliedFilters;
        private System.Windows.Forms.Button _cmdOK;
        private System.Windows.Forms.Button _cmdCancel;
        private System.Windows.Forms.Panel _pnlMain;
        
        internal PropertyOverridesDialog(
            IDeviceSpecificDesigner designer,
            int mergingContext
        ) : base(designer.UnderlyingControl.Site) {
            _designer = designer;
            _mergingContext = mergingContext;
           
            // Required for Win Form Designer support
            InitializeComponent();
 
            this._lblAppliedFilters.Text =
                SR.GetString(SR.PropertyOverridesDialog_AppliedDeviceFilters);
            this._btnEditFilters.Text = SR.GetString(SR.GenericDialog_Edit);
            this._lblProperties.Text =
                SR.GetString(SR.PropertyOverridesDialog_DeviceSpecificProperties);
            this._cmdOK.Text = SR.GetString(SR.GenericDialog_OKBtnCaption);
            this._cmdCancel.Text = SR.GetString(SR.GenericDialog_CancelBtnCaption);
 
            int tabOffset = GenericUI.InitDialog(
                this,
                _designer,
                _mergingContext
            );
 
            this.Text = _designer.UnderlyingControl.ID
                + " - " + SR.GetString(SR.PropertyOverridesDialog_Title);
            SetTabIndexes(tabOffset);
            _designer.SetDeviceSpecificEditor(this);
 
            // Note that the following can cause an
            // IDeviceSpecificDesigner.Refresh() to occur as a side-effect.
            _designer.RefreshHeader(_mergingContext);
            _ignoreSelectionChanged = false;
 
            // NOTE: Calling CurrentDeviceSpecificID will cause a refresh to
            //       happen as a side effect.
            _currentDeviceSpecificID = _designer.CurrentDeviceSpecificID;
            if(_currentDeviceSpecificID != null)
            {
                _cbChoices.Items.Clear();
                LoadChoices(_currentDeviceSpecificID);
                if(!ValidateLoadedChoices())
                {
                    // Throw to prevent dialog from opening.  Caught and hidden
                    // by PropertyOverridesTypeEditor.cs
                    throw new InvalidChoiceException(
                        "Property overrides dialog can not open because there " +
                        "are invalid choices defined in the page."
                    );
                }
            }
            
            // Register Event Handlers
            _cbChoices.SelectedIndexChanged += new EventHandler(
                OnFilterSelected
            );
            _btnEditFilters.Click += new EventHandler(OnEditFilters);
            _cmdOK.Click += new EventHandler(OnOK);
            _cmdCancel.Click += new EventHandler(OnCancel);
            UpdateUI();
        }
 
        protected override string HelpTopic {
            get {
                return "net.Mobile.PropertyOverridesDialog";
            }
        }
 
        internal void SetTabIndexes(int tabOffset)
        {
            this._pnlMain.TabIndex = ++tabOffset;
            this._lblAppliedFilters.TabIndex = ++tabOffset;
            this._cbChoices.TabIndex = ++tabOffset;
            this._btnEditFilters.TabIndex = ++tabOffset;
            this._lblProperties.TabIndex = ++tabOffset;
            this._pgProperties.TabIndex = ++tabOffset;
            this._cmdOK.TabIndex = ++tabOffset;
            this._cmdCancel.TabIndex = ++tabOffset;
        }
        
        private void InitializeComponent()
        {
            this._cbChoices = new System.Windows.Forms.ComboBox();
            this._cmdOK = new System.Windows.Forms.Button();
            this._btnEditFilters = new System.Windows.Forms.Button();
            this._pnlMain = new System.Windows.Forms.Panel();
            this._pgProperties = new System.Windows.Forms.PropertyGrid();
            this._lblProperties = new System.Windows.Forms.Label();
            this._lblAppliedFilters = new System.Windows.Forms.Label();
            this._cmdCancel = new System.Windows.Forms.Button();
            this._cbChoices.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this._cbChoices.DropDownWidth = 195;
            this._cbChoices.Location = new System.Drawing.Point(0, 16);
            this._cbChoices.Size = new System.Drawing.Size(195, 21);
            this._cmdOK.Location = new System.Drawing.Point(120, 290);
            this._cmdCancel.Location = new System.Drawing.Point(201, 290);
            this._btnEditFilters.Location = new System.Drawing.Point(201, 15);
            this._btnEditFilters.Size = new System.Drawing.Size(75, 23);
            this._pnlMain.Anchor = (System.Windows.Forms.AnchorStyles.Bottom
                | System.Windows.Forms.AnchorStyles.Left);
            this._pnlMain.Controls.AddRange(new System.Windows.Forms.Control[] {
                this._cmdCancel,
                this._cmdOK,
                this._lblProperties,
                this._pgProperties,
                this._btnEditFilters,
                this._cbChoices,
                this._lblAppliedFilters});
            this._pnlMain.Location = new System.Drawing.Point(6, 5);
            this._pnlMain.Size = new System.Drawing.Size(276, 313);
            this._pgProperties.CommandsVisibleIfAvailable = false;
            this._pgProperties.HelpVisible = false;
            this._pgProperties.LargeButtons = false;
            this._pgProperties.LineColor = System.Drawing.SystemColors.ScrollBar;
            this._pgProperties.Location = new System.Drawing.Point(0, 64);
            this._pgProperties.PropertySort = System.Windows.Forms.PropertySort.Alphabetical;
            this._pgProperties.Size = new System.Drawing.Size(275, 220);
            this._pgProperties.Text = "PropertyGrid";
            this._pgProperties.ToolbarVisible = false;
            this._pgProperties.ViewBackColor = System.Drawing.SystemColors.Window;
            this._pgProperties.ViewForeColor = System.Drawing.SystemColors.WindowText;
            this._pgProperties.PropertyValueChanged += new PropertyValueChangedEventHandler(this.OnPropertyValueChanged);
            this._lblProperties.Location = new System.Drawing.Point(0, 48);
            this._lblProperties.Size = new System.Drawing.Size(275, 16);
            this._lblAppliedFilters.Size = new System.Drawing.Size(275, 16);
            this.AcceptButton = _cmdOK;
            this.CancelButton = _cmdCancel;
            this.ClientSize = new System.Drawing.Size(285, 325);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {this._pnlMain});
        }
        
        private void CacheState(String deviceSpecificID)
        {
            _cachedDeviceSpecifics[deviceSpecificID] = 
                new PropertyOverridesCachedState(_cbChoices);
        }
 
        private void CacheCurrentState()
        {
            CacheState(_currentDeviceSpecificID);
        }
 
        private bool RestoreState(String deviceSpecificID)
        {
            if (null != deviceSpecificID)
            {
                _currentDeviceSpecificID = deviceSpecificID.ToLower(CultureInfo.InvariantCulture);
                PropertyOverridesCachedState state =
                    (PropertyOverridesCachedState) _cachedDeviceSpecifics[
                        _currentDeviceSpecificID
                    ];
                if(state != null)
                {
                    state.Restore(_cbChoices);
                    foreach(ChoiceTreeNode node in state.Choices)
                    {
                        node.Choice.Refresh();
                    }
                    return true;
                }
            }
            else
            {
                _currentDeviceSpecificID = null;
            }
            return false;
        }
 
        [Conditional("DEBUG")]
        private void debug_CheckChoicesForDuplicate(DeviceSpecificChoice runtimeChoice)
        {
            foreach(ChoiceTreeNode choiceNode in _cbChoices.Items)
            {
                if(choiceNode.Name == runtimeChoice.Filter
                    && choiceNode.RuntimeChoice.Argument == runtimeChoice.Argument)
                {
                    Debug.Fail("Loaded duplicate choice: " + 
                        DesignerUtility.ChoiceToUniqueIdentifier(runtimeChoice));
                }
            }
        }
 
        private void LoadChoices(String deviceSpecificID)
        {
            DeviceSpecific ds;
            _designer.GetDeviceSpecific(deviceSpecificID, out ds);
            LoadChoices(ds);
        }
        
        private void LoadChoices(DeviceSpecific deviceSpecific)
        {
            if(deviceSpecific != null)
            {
                foreach(DeviceSpecificChoice runtimeChoice in deviceSpecific.Choices)
                {
                    debug_CheckChoicesForDuplicate(runtimeChoice);
                    ChoiceTreeNode newChoiceNode = new ChoiceTreeNode(
                        null,
                        runtimeChoice,
                        _designer
                    );
                    newChoiceNode.IncludeArgument = true;
                    _cbChoices.Items.Add(newChoiceNode);
                }
            }
            UpdateUI();
        }
 
        private bool ValidateLoadedChoices()
        {
            StringCollection duplicateChoices =
                DesignerUtility.GetDuplicateChoiceTreeNodes(
                    _cbChoices.Items
                );
 
            if(duplicateChoices.Count > 0)
            {
                if (!_ignoreSelectionChanged)
                {
                    GenericUI.ShowWarningMessage(
                        SR.GetString(SR.PropertyOverridesDialog_Title),
                        SR.GetString(SR.PropertyOverridesDialog_DuplicateChoices,
                        GenericUI.BuildCommaDelimitedList(duplicateChoices))
                        );
                }
                return false;
            }
            return true;
        }
        
        private void SaveChoices()
        {
            if(_currentDeviceSpecificID != null)
            {
                CacheCurrentState();
            }
            foreach (DictionaryEntry entry in _cachedDeviceSpecifics)
            {
                PropertyOverridesCachedState state =
                    (PropertyOverridesCachedState) entry.Value;
                state.SaveChoicesFromComboBox(
                    _designer,
                    (String) entry.Key
                );
            }
        }
        
        private void UpdateUI()
        {
            if(_cbChoices.SelectedItem == null && _cbChoices.Items.Count > 0)
            {
                _cbChoices.SelectedItem = _cbChoices.Items[0];
            }
            
            ChoiceTreeNode choice = (ChoiceTreeNode) _cbChoices.SelectedItem;
            bool isChoiceSelected = (choice != null);
            if (isChoiceSelected)
            {
                _cbChoices.Text = choice.ToString();
                _pgProperties.SelectedObject =
                    choice.Choice;
            }
            else
            {
                _cbChoices.Text = String.Empty;
                _pgProperties.SelectedObject = null;
            }
            _cbChoices.Enabled = isChoiceSelected;
            _pgProperties.Enabled = isChoiceSelected;
            _btnEditFilters.Enabled = (_currentDeviceSpecificID != null);
        }
 
        private void SetDirty(bool dirty)
        {
            if (dirty)
            {
                if (false == _isDirty)
                {
                    _isDirty = true;
                    _cmdCancel.Text = SR.GetString(SR.GenericDialog_CancelBtnCaption);
                }
            }
            else
            {
                if (true == _isDirty)
                {
                    _isDirty = false;
                    _cmdCancel.Text = SR.GetString(SR.GenericDialog_CloseBtnCaption);
                }
            }
        }
 
        ////////////////////////////////////////////////////////////////////////
        //  Begin Event Handling
        ////////////////////////////////////////////////////////////////////////
 
        private void OnEditFilters(Object sender, EventArgs e)
        {
            ISite componentSite = ((IComponent)(_designer.UnderlyingControl)).Site;
            Debug.Assert(componentSite != null, "Expected the runtime control to be sited.");
 
            IComponentChangeService changeService =
                (IComponentChangeService)componentSite.GetService(typeof(IComponentChangeService));
 
            IMobileWebFormServices wfServices = 
                (IMobileWebFormServices)componentSite.GetService(typeof(IMobileWebFormServices));
 
            DialogResult result = DialogResult.Cancel;
            try
            {
                AppliedDeviceFiltersDialog dialog = new
                    AppliedDeviceFiltersDialog(
                    this,
                    _mergingContext
                    );
 
                result = dialog.ShowDialog();
            }
            finally
            {
                if (result != DialogResult.Cancel)
                {
                    SaveChoices();
                    SetDirty(false);
 
                    if (changeService != null)
                    {
                        changeService.OnComponentChanged(_designer.UnderlyingControl, null, null, null);
                    }
                }
            }
        }
        
        private void OnFilterSelected(Object sender, EventArgs e)
        {
            UpdateUI();
        }
 
        private void OnOK(Object sender, EventArgs e)
        {
            SaveChoices();
            Close();
            DialogResult = DialogResult.OK;
        }
        
        private void OnCancel(Object sender, EventArgs e)
        {
            Close();
            DialogResult = DialogResult.Cancel;
        }
        
        private void OnPropertyValueChanged(Object sender, 
                                            PropertyValueChangedEventArgs e)
        {
            SetDirty(true);
        }
 
        ////////////////////////////////////////////////////////////////////////
        //  End Event Handling
        ////////////////////////////////////////////////////////////////////////
 
        ////////////////////////////////////////////////////////////////////////
        //  Begin IRefreshableComponentEditor Implementation
        ////////////////////////////////////////////////////////////////////////
 
        bool IRefreshableDeviceSpecificEditor.RequestRefresh()
        {
            return true;
        }
        
        void IRefreshableDeviceSpecificEditor.Refresh(
            String deviceSpecificID,
            DeviceSpecific deviceSpecific
        ) {
            if (_currentDeviceSpecificID != null)
            {
                CacheCurrentState();
            }
            _cbChoices.Items.Clear();
 
            if (!RestoreState(deviceSpecificID))
            {
                LoadChoices(deviceSpecific);
                if(!ValidateLoadedChoices())
                {
                    _designer.RefreshHeader(
                        MobileControlDesigner.MergingContextProperties
                    );
                }
            }
            UpdateUI();
        }
 
        void IRefreshableDeviceSpecificEditor.UnderlyingObjectsChanged()
        {
            SaveChoices();
            SetDirty(false);
        }
 
        private bool InExternalCacheEditMode
        {
            get
            {
                return _cacheBuffer != null;
            }
        }
        
        private IDictionary _cacheBuffer = null;
        
        void IRefreshableDeviceSpecificEditor.BeginExternalDeviceSpecificEdit()
        {
            Debug.Assert(!InExternalCacheEditMode,
                "Call to BeginExternalDeviceSpecificEdit() while already in external "
                + "cache edit mode.");
            if(_currentDeviceSpecificID != null)
            {
                CacheCurrentState();
                _currentDeviceSpecificID = null;
            }
            _cacheBuffer = new HybridDictionary(
                true /* make case-insensitive*/ );
            foreach(DictionaryEntry entry in _cachedDeviceSpecifics)
            {
                _cacheBuffer.Add(entry.Key, entry.Value);
            }
        }
        
        void IRefreshableDeviceSpecificEditor.EndExternalDeviceSpecificEdit(
            bool commitChanges)
        {
            Debug.Assert(InExternalCacheEditMode,
                "Call to EndExternalDeviceSpecificEdit() while not in external "
                + "cache edit mode.");
            if(commitChanges)
            {
                _cachedDeviceSpecifics = _cacheBuffer;
            }
            _cacheBuffer = null;
        }
        
        void IRefreshableDeviceSpecificEditor.DeviceSpecificRenamed(
            String oldDeviceSpecificID, String newDeviceSpecificID)
        {
            Debug.Assert(InExternalCacheEditMode,
                "Call to DeviceSpecificRenamed() while not in external "
                + "cache edit mode.");
            Object value = _cacheBuffer[oldDeviceSpecificID];
            if(value != null)
            {
                _cacheBuffer.Remove(oldDeviceSpecificID);
                _cacheBuffer.Add(newDeviceSpecificID, value);
            }
        }
        
        void IRefreshableDeviceSpecificEditor.DeviceSpecificDeleted(
            String deviceSpecificID)
        {
            Debug.Assert(InExternalCacheEditMode,
                "Call to DeviceSpecificDeleted() while not in external "
                + "cache edit mode.");
            _cacheBuffer.Remove(deviceSpecificID);
        }
 
        ////////////////////////////////////////////////////////////////////////
        //  End IRefreshableComponentEditor Implementation
        ////////////////////////////////////////////////////////////////////////
 
        ////////////////////////////////////////////////////////////////////////
        //  Begin IDeviceSpecificDesigner Implementation
        ////////////////////////////////////////////////////////////////////////
 
        void IDeviceSpecificDesigner.SetDeviceSpecificEditor
            (IRefreshableDeviceSpecificEditor editor)
        {
        }
 
        String IDeviceSpecificDesigner.CurrentDeviceSpecificID
        {
            get
            {
                return _currentDeviceSpecificID;
            }
        }
 
        System.Windows.Forms.Control IDeviceSpecificDesigner.Header
        {
            get
            {
                return _header;
            }
        }
 
        System.Web.UI.Control IDeviceSpecificDesigner.UnderlyingControl
        {
            get
            {
                return _designer.UnderlyingControl;
            }
        }
 
        Object IDeviceSpecificDesigner.UnderlyingObject
        {
            get
            {
                return _designer.UnderlyingObject;
            }
        }
 
        bool IDeviceSpecificDesigner.GetDeviceSpecific(String deviceSpecificParentID, out DeviceSpecific ds)
        {
            Debug.Assert(deviceSpecificParentID == _currentDeviceSpecificID);
 
            ds = null;
            if (_cbChoices.Items.Count > 0)
            {
                ds = new DeviceSpecific();
                foreach (ChoiceTreeNode choiceNode in _cbChoices.Items)
                {
                    DeviceSpecificChoice choice = choiceNode.Choice.RuntimeChoice;
                    ds.Choices.Add(choice);
                }
            }
 
            return true;
        }
 
        void IDeviceSpecificDesigner.SetDeviceSpecific(String deviceSpecificParentID, DeviceSpecific ds)
        {
            Debug.Assert(_currentDeviceSpecificID != null);
            _cbChoices.Items.Clear();
            LoadChoices(ds);
            UpdateUI();
        }
 
        void IDeviceSpecificDesigner.InitHeader(int mergingContext)
        {
            HeaderPanel panel = new HeaderPanel();
            HeaderLabel lblDescription = new HeaderLabel();
 
            lblDescription.TabIndex = 0;
            lblDescription.Text = SR.GetString(SR.MobileControl_SettingGenericChoiceDescription);
            
            panel.Height = lblDescription.Height;
            panel.Width = lblDescription.Width;
            panel.Controls.Add(lblDescription);
            _header = panel;
        }
 
        void IDeviceSpecificDesigner.RefreshHeader(int mergingContext)
        {
        }
 
        void IDeviceSpecificDesigner.UseCurrentDeviceSpecificID()
        {
        }
 
        /////////////////////////////////////////////////////////////////////////
        //  End IDeviceSpecificDesigner Implementation
        /////////////////////////////////////////////////////////////////////////
    }
    
    ////////////////////////////////////////////////////////////////////////////
    //  Begin Internal Class
    ////////////////////////////////////////////////////////////////////////////
 
    [
        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 class ChoicePropertyFilter :
        ICustomTypeDescriptor, IDeviceSpecificChoiceDesigner, IComponent
    {
        private DeviceSpecificChoice _choice;
        private Object _copyOfOriginalObject;
        private Object _underlyingObject;
        private IDeviceSpecificDesigner _designer;
        private Hashtable _specialProp_buffer = new Hashtable();
        private EventHandler _specialProp_delegate = null;
        private ISite _site = null;
        private EventHandlerList _events;
        private static readonly Object _eventDisposed = new Object();
        private static readonly String _alternateUrl = "AlternateUrl";
        private static readonly String _navigateUrl = "NavigateUrl";
 
        internal ChoicePropertyFilter(
            DeviceSpecificChoice choice,
            IDeviceSpecificDesigner designer,
            ISite site
        ) {
            _events = new EventHandlerList();
            _choice = choice;
            _site = site;
            _designer = designer;
 
            CreateLocalCopiesOfObjects();
        }
 
        private void CreateLocalCopiesOfObjects()
        {
            // We make this copy of the original to remove the object from
            // the inheritance chain.
            _copyOfOriginalObject = CloneTarget(_designer.UnderlyingObject);
            _underlyingObject = CloneTarget(_designer.UnderlyingObject);
            
            // We need to pop up editors when certain property values change    
            RegisterForPropertyChangeEvents();
            
            // Copy properties set on DeviceSpecificChoice
            ApplyChoiceToRuntimeControl();
        }
 
        internal void Refresh()
        {
            ApplyChangesToRuntimeChoice();
            CreateLocalCopiesOfObjects();
        }
 
        private void RegisterForPropertyChangeEvents()
        {
            foreach(PropertyDescriptor property in TypeDescriptor.GetProperties(
                _underlyingObject.GetType()
            )) {
                if(property.Converter is NavigateUrlConverter &&
                   (property.Name == _navigateUrl ||
                    property.Name == _alternateUrl))
                {
                    // 
 
                    if (property.Name == _navigateUrl)
                    {
                        _specialProp_delegate = new EventHandler(OnNavigateUrlChanged);
                    }
                    else
                    {
                        _specialProp_delegate = new EventHandler(OnAlternateUrlChanged);
                    }
                    _specialProp_buffer[property.Name] = property.GetValue(_underlyingObject);
                    property.AddValueChanged(
                        _underlyingObject,
                        _specialProp_delegate
                    );
                }
            }
        }
        
        private Object CloneTarget(Object target)
        {
            Object clone = Activator.CreateInstance(
                target.GetType()
            );
            // We need to copy the Site over to the new object incase setting
            // properties has a side effect that requires the component model
            // to be intact.  (e.g., Launching UrlPicker for NavigateUrl).
            if(clone is IComponent)
            {
                ((IComponent)clone).Site = ((IComponent)target).Site;
            }
            // We also need to copy the Page over in case runtime properties
            // try to access the page.
            if(clone is System.Web.UI.Control)
            {
                ((Control)clone).Page = ((Control)target).Page;
            }
            CopyOverridableProperties(target, clone);
            return clone;
        }
        
        private void CopyStyleProperties(Style source, Style dest)
        {
            // We copy the StateBag to duplicate the style properties without
            // walking the inheritance.
            dest.State.Clear();
            foreach(String key in source.State.Keys)
            {
                dest.State[key] = source.State[key];
            }
        }
 
        private void CopyOverridableProperties(Object source, Object dest)
        {
            MobileControl destControl = null;
           
            // HACK: To avoid copying expandable property FontInfo.  We will
            //       need to required that expandable properties implement
            //       ICloneable for our designer extensibility story.
            if(source is Style)
            {
                CopyStyleProperties((Style)source, (Style)dest);
                return;
            }
            
            if(source is MobileControl)
            {
                // If the control is a MobileControl, we copy the style's
                // StateBag to get the non-inherited proprety values.
                destControl = (MobileControl) dest;
                MobileControl sourceControl = (MobileControl) source;
                CopyStyleProperties(sourceControl.Style, destControl.Style);
            }
 
            // Copy remaining properties not contained in the style (or
            // all properties if not a mobile control.)
            PropertyDescriptorCollection properties = 
                TypeDescriptor.GetProperties(dest.GetType());
            foreach(PropertyDescriptor property in properties)
            {
                if(IsDeviceOverridable(property)
                    && (destControl == null
                    || !PropertyExistsInStyle(property, destControl.Style)))
                {
                    CopyProperty(property, source, dest);
                }
            }
        }
 
        private void CopyProperty(PropertyDescriptor property,
            Object source,
            Object dest)
        {
            Object value = property.GetValue(source);
 
            if(property.Converter is ExpandableObjectConverter)
            {
                if(value is ICloneable)
                {
                    value = ((ICloneable)value).Clone();
                }
                else
                {
                    throw new Exception(
                        SR.GetString(
                            SR.PropertyOverridesDialog_NotICloneable,
                            property.Name,
                            property.PropertyType.FullName
                        )
                    );
                }
            }
            property.SetValue(dest, value);
        }
        
        private bool PropertyExistsInStyle(
            PropertyDescriptor property, Style style)
        {
            return style.GetType().GetProperty(property.Name) != null;
        }
        
        public event EventHandler Disposed 
        {
            add 
            {
                _events.AddHandler(_eventDisposed, value);
            }
            remove 
            {
                _events.RemoveHandler(_eventDisposed, value);
            }
        }
 
        public ISite Site
        {
            get
            {
                Debug.Assert(_site != null);
                return _site;
            }
 
            set
            {
                _site = value;
            }
        }
 
        public void Dispose()
        {
            if (_events != null) 
            {
                EventHandler handler = (EventHandler)_events[_eventDisposed];
                if (handler != null) handler(this, EventArgs.Empty);
            }
        }
 
        private void OnNavigateUrlChanged(Object sender, EventArgs e)
        {
            OnSpecialPropertyChanged(sender, true);
        }
 
        private void OnAlternateUrlChanged(Object sender, EventArgs e)
        {
            OnSpecialPropertyChanged(sender, false);
        }
 
        // 
 
 
        private void OnSpecialPropertyChanged(Object sender, bool navigateUrl)
        {
            IComponent component = (IComponent) sender;
            PropertyDescriptor property
                = TypeDescriptor.GetProperties(component)[navigateUrl ? _navigateUrl : _alternateUrl];
            String newValue = (String) property.GetValue(component);
            String oldValue = (String) _specialProp_buffer[navigateUrl ? _navigateUrl : _alternateUrl];
            newValue = NavigateUrlConverter.GetUrl(
                component,
                newValue,
                oldValue
            );
            property.RemoveValueChanged(
                _underlyingObject,
                _specialProp_delegate
            );
            property.SetValue(component, newValue);
            property.AddValueChanged(
                _underlyingObject,
                _specialProp_delegate
            );
        }
 
        private static bool IsDeviceOverridable(PropertyDescriptor property)
        {
            // 
            return (
                property.IsBrowsable
                && ((!property.IsReadOnly)
                    || (property.Converter is ExpandableObjectConverter))
                && !property.SerializationVisibility.Equals(
                     DesignerSerializationVisibility.Hidden)
                && property.Name != "ID"
            );
        }
 
        private void ApplyChoiceToRuntimeControl()
        {
            PropertyDescriptorCollection properties =
                TypeDescriptor.GetProperties(
                    _underlyingObject.GetType());
 
            foreach(PropertyDescriptor property in properties)
            {
                if(IsDeviceOverridable(property))
                {
                    ApplyChoiceToRuntimeControl_helper(
                        property,
                        _underlyingObject,
                        ""
                    );
                }
            }
        }
 
        private void ApplyChoiceToRuntimeControl_helper(
            PropertyDescriptor property,
            Object target,
            String prefix
        ) {
            String propertyName = prefix + property.Name;
            String value = ((IAttributeAccessor)_choice).GetAttribute(propertyName) as String;
 
            if(property.Converter is ExpandableObjectConverter)
            {
                PropertyDescriptorCollection properties =
                    TypeDescriptor.GetProperties(
                        property.PropertyType
                    );
                foreach(PropertyDescriptor embeddedProperty in properties)
                {
                    if(IsDeviceOverridable(embeddedProperty))
                    {
                        ApplyChoiceToRuntimeControl_helper(
                            embeddedProperty,
                            property.GetValue(target),
                            propertyName + "-"
                        );
                    }
                }
                return;
            }
            
            if(value != null)
            {
                try
                {
                    property.SetValue(
                        target,
                        property.Converter.ConvertFromString(value)
                    );
                }
                catch
                {
                    GenericUI.ShowWarningMessage(
                        SR.GetString(SR.PropertyOverridesDialog_Title),
                        SR.GetString(
                            SR.PropertyOverridesDialog_InvalidPropertyValue,
                            value,
                            propertyName
                        )
                    );
                }
            }
        }
 
        private void ApplyChangesToRuntimeChoice()
        {
            PropertyDescriptorCollection properties =
                TypeDescriptor.GetProperties(
                    _underlyingObject.GetType()
                );
 
            foreach(PropertyDescriptor property in properties)
            {
                if (IsDeviceOverridable(property))
                {
                    ApplyChangesToRuntimeChoice_helper(
                        property,
                        _copyOfOriginalObject,
                        _underlyingObject,
                        "");
                }
            }
        }
        
        private void ApplyChangesToRuntimeChoice_helper(
            PropertyDescriptor property,
            Object sourceTarget,
            Object destTarget,
            String prefix
        ) {
            Object oldValue = property.GetValue(sourceTarget);
            Object newValue = property.GetValue(destTarget);
 
            String propertyName = prefix + property.Name;
         
            if(property.Converter is ExpandableObjectConverter)
            {
                PropertyDescriptorCollection properties =
                    TypeDescriptor.GetProperties(
                        newValue.GetType()
                    );
                foreach(PropertyDescriptor embeddedProperty in properties)
                {
                    if(IsDeviceOverridable(embeddedProperty))
                    {
                        ApplyChangesToRuntimeChoice_helper(
                            embeddedProperty,
                            oldValue,
                            newValue,
                            propertyName + "-"
                        );
                    }
                }
            }
            else if(IsDeviceOverridable(property))
            {
                IAttributeAccessor overrides = (IAttributeAccessor)_choice;
                String oldValueString =
                    property.Converter.ConvertToInvariantString(
                        oldValue
                    );
                String newValueString =
                    property.Converter.ConvertToInvariantString(
                        newValue
                    );                
                if(newValueString != oldValueString)
                {
                    overrides.SetAttribute(propertyName, newValueString);
                }
                else
                {
                    // Clear any previous values we might have loaded
                    overrides.SetAttribute(propertyName, null);
                }
            }
        }
 
        internal DeviceSpecificChoice RuntimeChoice
        {
            get
            {
                ApplyChangesToRuntimeChoice();
                return _choice;
            }
        }
 
        internal IDeviceSpecificDesigner Designer
        {
            get
            {
                return _designer;
            }
        }
 
        internal Object Owner
        {
            get
            {
                return _underlyingObject;
            }
        }
 
        private PropertyDescriptorCollection PreFilterProperties(
            PropertyDescriptorCollection originalProperties
        ) {
            PropertyDescriptorCollection newProperties =
                new PropertyDescriptorCollection(
                    new PropertyDescriptor[] {}
                );
            
            foreach(PropertyDescriptor property in originalProperties)
            {
                if (IsDeviceOverridable(property))
                {
                    newProperties.Add(property);
                }
            }
 
            PropertyDescriptor[] arpd = new PropertyDescriptor[newProperties.Count];
            for(int i = 0; i < newProperties.Count; i++)
            {
                arpd[i] = newProperties[i];
            }
            newProperties = new PropertyDescriptorCollection(arpd);
 
            return newProperties;
        }
 
        ////////////////////////////////////////////////////////////////////
        //  Begin ICustomTypeDescriptor Implementation
        ////////////////////////////////////////////////////////////////////
 
        System.ComponentModel.AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this.GetType());
        }
 
        String ICustomTypeDescriptor.GetClassName()
        {
            return TypeDescriptor.GetClassName(this.GetType());
        }
 
        String ICustomTypeDescriptor.GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this.GetType());
        }
 
        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return TypeDescriptor.GetConverter(this.GetType());
        }
 
        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this.GetType());
        }
 
        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this.GetType());
        }
 
        Object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this.GetType(), editorBaseType);
        }
 
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return TypeDescriptor.GetEvents(this.GetType());
        }
 
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this.GetType(), attributes);
        }
 
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            PropertyDescriptorCollection collection =
                TypeDescriptor.GetProperties(
                    _underlyingObject.GetType()
                );
            collection = PreFilterProperties(collection);
            return collection;
        }
 
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection collection =
                TypeDescriptor.GetProperties(
                    _underlyingObject.GetType(),
                    attributes
                );
            collection = PreFilterProperties(collection);
            return collection;
        }
 
        Object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor property)
        {
            return _underlyingObject;
        }
 
        ////////////////////////////////////////////////////////////////////////
        //  End ICustomTypeDescriptor Implementation
        ////////////////////////////////////////////////////////////////////////
 
        ///////////////////////////////////////////////////////////////////////
        //  Begin IDeviceSpecificChoiceDesigner Implementation
        ///////////////////////////////////////////////////////////////////////
 
        Object IDeviceSpecificChoiceDesigner.UnderlyingObject
        {
            get
            {
                return _designer.UnderlyingObject;
            }
        }
 
        Control IDeviceSpecificChoiceDesigner.UnderlyingControl
        {
            get
            {
                return _designer.UnderlyingControl;
            }
        }
        
        ///////////////////////////////////////////////////////////////////////
        //  End IDeviceSpecificChoiceDesigner Implementation
        ///////////////////////////////////////////////////////////////////////
    }
 
    ////////////////////////////////////////////////////////////////////////////
    //  End Internal Class
    ////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////
    //  Begin Internal Class
    /////////////////////////////////////////////////////////////////////////
 
    [
        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 class PropertyOverridesCachedState : DeviceSpecificDialogCachedState
    {
        private ArrayList _cachedComboBox = null;
        
        internal PropertyOverridesCachedState(
            ComboBox comboBox
        ) {
            _cachedComboBox = new ArrayList();
            foreach(Object o in comboBox.Items)
            {
                _cachedComboBox.Add(o);
            }
        }
        
        internal void Restore(
            ComboBox comboBox
        ) {
            Object selectedItem = comboBox.SelectedItem;
            comboBox.Items.Clear();
            comboBox.Items.AddRange(_cachedComboBox.ToArray());
            if(selectedItem != null)
            {
                int index = comboBox.Items.IndexOf(selectedItem);
                if(index >= 0)
                {
                    comboBox.SelectedItem = comboBox.Items[index];
                }
            }
        }
 
        internal bool FilterExistsInComboBox(DeviceFilterNode filter)
        {
            foreach(DeviceFilterNode availableFilter in _cachedComboBox)
            {
                if(availableFilter.Name == filter.Name)
                {
                    return true;
                }
            }
            return false;
        }
 
        internal void SaveChoicesFromComboBox(
            IDeviceSpecificDesigner designer,
            String deviceSpecificID
        ) {
            SaveChoices(designer, deviceSpecificID, _cachedComboBox);
        }
 
        internal ArrayList Choices
        {
            get
            {
                return _cachedComboBox;
            }
        }
    }
    
    /////////////////////////////////////////////////////////////////////////
    //  End Internal Class
    /////////////////////////////////////////////////////////////////////////
 
    /////////////////////////////////////////////////////////////////////////
    //  Begin Internal Class
    /////////////////////////////////////////////////////////////////////////
 
    [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 class InvalidChoiceException : ApplicationException
    {
        internal InvalidChoiceException(String message) : base(message)
        {
        }
    }
    
    /////////////////////////////////////////////////////////////////////////
    //  End Internal Class
    /////////////////////////////////////////////////////////////////////////
}