// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Windows.Forms.Design.Behavior;
using Microsoft.Win32;

namespace System.Windows.Forms.Design;

/// <summary>
///  Provides the component tray UI for the form designer.
/// </summary>
[ToolboxItem(false)]
[DesignTimeVisible(false)]
[ProvideProperty("Location", typeof(IComponent))]
[ProvideProperty("TrayLocation", typeof(IComponent))]
public class ComponentTray : ScrollableControl, IExtenderProvider, ISelectionUIHandler, IOleDragClient
{
    private static Point InvalidPoint { get; } = new(int.MinValue, int.MinValue);
    private IServiceProvider _serviceProvider; // Where services come from.
    private Point _whiteSpace = Point.Empty; // space to leave between components.
    private Size _grabHandle = Size.Empty; // Size of the grab handles.

    private List<Control> _controls; // List of items in the tray in the order of their layout.
    private SelectionUIHandler _dragHandler; // the thing responsible for handling mouse drags
    private ISelectionUIService _selectionUISvc; // selection UI; we use this a lot
    private IToolboxService _toolboxService; // cached for drag/drop

    /// <summary>
    ///  Provides drag and drop functionality through OLE.
    /// </summary>
    internal OleDragDropHandler _oleDragDropHandler; // handler class for ole drag drop operations.

    private readonly IDesigner _mainDesigner; // the designer that is associated with this tray
    private IEventHandlerService _eventHandlerService; // Event Handler service to handle keyboard and focus.
    private bool _queriedTabOrder;
    private MenuCommand _tabOrderCommand;
    private ICollection _selectedObjects;

    // Services that we use on a high enough frequency to merit caching.
    private IMenuCommandService _menuCommandService;
    private InheritanceUI _inheritanceUI;

    private Point _mouseDragStart = InvalidPoint; // the starting location of a drag
    private Point _mouseDragEnd = InvalidPoint; // the ending location of a drag
    private Rectangle _mouseDragWorkspace = Rectangle.Empty; // a temp work rectangle we cache for perf
    private ToolboxItem _mouseDragTool; // the tool that's being dragged; only for drag/drop
    private Point _mouseDropLocation = InvalidPoint; // where the tool was dropped
    private bool _showLargeIcons; // Show Large icons or not.
    private bool _autoArrange; // allows for auto arranging icons.
    private Point _autoScrollPosBeforeDragging = Point.Empty; // Used to return the correct scroll pos. after a drag

    // Component Tray Context menu items...
    private readonly MenuCommand _menucmdArrangeIcons;
    private readonly MenuCommand _menucmdLineupIcons;
    private readonly MenuCommand _menucmdLargeIcons;
    private bool _fResetAmbient;
    private bool _fSelectionChanged;
    private ComponentTrayGlyphManager _glyphManager; // used to manage any glyphs added to the tray

    // Empty class for build time dependancy

    /// <summary>
    ///  Creates a new component tray.  The component tray
    ///  will monitor component additions and removals and create
    ///  appropriate UI objects in its space.
    /// </summary>
    public ComponentTray(IDesigner mainDesigner, IServiceProvider serviceProvider)
    {
        AutoScroll = true;
        _mainDesigner = mainDesigner;
        _serviceProvider = serviceProvider;
        AllowDrop = true;
        Text = "ComponentTray"; // makes debugging easier
        SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
        _controls = [];
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        IExtenderProviderService es = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));
        Debug.Assert(es is not null, "Component tray wants an extender provider service, but there isn't one.");
        es?.AddExtenderProvider(this);

        if (GetService(typeof(IEventHandlerService)) is null)
        {
            if (host is not null)
            {
                _eventHandlerService = new EventHandlerService(this);
                host.AddService(_eventHandlerService);
            }
        }

        IMenuCommandService mcs = MenuService;
        if (mcs is not null)
        {
            Debug.Assert(_menucmdArrangeIcons is null, "Non-Null Menu Command for ArrangeIcons");
            Debug.Assert(_menucmdLineupIcons is null, "Non-Null Menu Command for LineupIcons");
            Debug.Assert(_menucmdLargeIcons is null, "Non-Null Menu Command for LargeIcons");
            _menucmdArrangeIcons = new MenuCommand(new EventHandler(OnMenuArrangeIcons), StandardCommands.ArrangeIcons);
            _menucmdLineupIcons = new MenuCommand(new EventHandler(OnMenuLineupIcons), StandardCommands.LineupIcons);
            _menucmdLargeIcons = new MenuCommand(new EventHandler(OnMenuShowLargeIcons), StandardCommands.ShowLargeIcons);
            _menucmdArrangeIcons.Checked = AutoArrange;
            _menucmdLargeIcons.Checked = ShowLargeIcons;
            mcs.AddCommand(_menucmdArrangeIcons);
            mcs.AddCommand(_menucmdLineupIcons);
            mcs.AddCommand(_menucmdLargeIcons);
        }

        IComponentChangeService componentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
        if (componentChangeService is not null)
        {
            componentChangeService.ComponentRemoved += new ComponentEventHandler(OnComponentRemoved);
        }

        if (GetService(typeof(IUIService)) is IUIService uiService)
        {
            Color styleColor;
            if (uiService.Styles["ArtboardBackground"] is Color backgroundColor)
            {
                styleColor = backgroundColor;
            }

            // Can't use 'as' here since Color is a value type
            else if (uiService.Styles["VsColorDesignerTray"] is Color designerTrayColor)
            {
                styleColor = designerTrayColor;
            }
            else if (uiService.Styles["HighlightColor"] is Color highlightColor)
            {
                // Since v1, we have had code here that checks for HighlightColor, so some hosts (like WinRes) have been setting it. If VsColorDesignerTray isn't present, we look for HighlightColor for backward compat.
                styleColor = highlightColor;
            }
            else
            {
                // No style color provided? Let's pick a default.
                styleColor = SystemColors.Info;
            }

            if (uiService.Styles["ArtboardBackgroundText"] is Color backgroundTextColor)
            {
                ForeColor = backgroundTextColor;
            }
            else if (uiService.Styles["VsColorPanelText"] is Color panelTextColor)
            {
                ForeColor = panelTextColor;
            }

            BackColor = styleColor;
            Font = (Font)uiService.Styles["DialogFont"];
        }

        ISelectionService selSvc = (ISelectionService)GetService(typeof(ISelectionService));
        if (selSvc is not null)
        {
            selSvc.SelectionChanged += new EventHandler(OnSelectionChanged);
        }

        // Listen to the SystemEvents so that we can resync selection based on display settings etc.
        SystemEvents.DisplaySettingsChanged += new EventHandler(OnSystemSettingChanged);
        SystemEvents.InstalledFontsChanged += new EventHandler(OnSystemSettingChanged);
        SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged);

        if (GetService(typeof(BehaviorService)) is BehaviorService behSvc)
        {
            // this object will manage any glyphs that get added to our tray
            _glyphManager = new ComponentTrayGlyphManager(behSvc);
        }
    }

    private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
    {
        if (IsHandleCreated)
        {
            _fResetAmbient = true;
            ResetTrayControls();
            BeginInvoke(new AsyncInvokeHandler(Invalidate), [true]);
        }
    }

    private void OnComponentRefresh(RefreshEventArgs e)
    {
        if (e.ComponentChanged is IComponent component)
        {
            TrayControl control = TrayControl.FromComponent(component);
            if (control is not null)
            {
                bool shouldDisplay = CanDisplayComponent(component);
                if (shouldDisplay != control.Visible || !shouldDisplay)
                {
                    control.Visible = shouldDisplay;
                    Rectangle bounds = control.Bounds;
                    bounds.Inflate(_grabHandle);
                    bounds.Inflate(_grabHandle);
                    Invalidate(bounds);
                    PerformLayout();
                }
            }
        }
    }

    private void OnSystemSettingChanged(object sender, EventArgs e)
    {
        if (IsHandleCreated)
        {
            _fResetAmbient = true;
            ResetTrayControls();
            BeginInvoke(new AsyncInvokeHandler(Invalidate), [true]);
        }
    }

    private void ResetTrayControls()
    {
        ControlCollection children = Controls;
        if (children is null)
        {
            return;
        }

        for (int i = 0; i < children.Count; ++i)
        {
            if (children[i] is TrayControl tc)
            {
                tc._fRecompute = true;
            }
        }
    }

    private delegate void AsyncInvokeHandler(bool children);

    private void OnSelectionChanged(object sender, EventArgs e)
    {
        _selectedObjects = ((ISelectionService)sender).GetSelectedComponents();
        object primary = ((ISelectionService)sender).PrimarySelection;
        Invalidate();
        _fSelectionChanged = true;

        // Accessibility information
        foreach (object selObj in _selectedObjects)
        {
            if (selObj is IComponent component)
            {
                Control c = TrayControl.FromComponent(component);
                if (c is not null)
                {
                    PInvoke.NotifyWinEvent(
                        (uint)AccessibleEvents.SelectionAdd,
                        c,
                        (int)OBJECT_IDENTIFIER.OBJID_CLIENT,
                        (int)PInvoke.CHILDID_SELF);
                }
            }
        }

        if (primary is IComponent comp)
        {
            Control c = TrayControl.FromComponent(comp);
            if (c is not null && IsHandleCreated)
            {
                ScrollControlIntoView(c);
                PInvoke.NotifyWinEvent(
                    (uint)AccessibleEvents.Focus,
                    c,
                    (int)OBJECT_IDENTIFIER.OBJID_CLIENT,
                    (int)PInvoke.CHILDID_SELF);
            }

            if (_glyphManager is not null)
            {
                _glyphManager.SelectionGlyphs.Clear();
                IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
                foreach (object selObj in _selectedObjects)
                {
                    if (selObj is IComponent selectedComponent && !(host.GetDesigner(selectedComponent) is ControlDesigner))
                    { // don't want to do it for controls that are also in the tray
                        GlyphCollection glyphs = _glyphManager.GetGlyphsForComponent(selectedComponent);
                        if (glyphs is not null && glyphs.Count > 0)
                        {
                            SelectionGlyphs.AddRange(glyphs);
                        }
                    }
                }
            }
        }
    }

    private void OnComponentRemoved(object sender, ComponentEventArgs cevent)
    {
        RemoveComponent(cevent.Component);
    }

    private void OnMenuShowLargeIcons(object sender, EventArgs e)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        DesignerTransaction t = null;
        try
        {
            t = host.CreateTransaction(SR.TrayShowLargeIcons);
            PropertyDescriptor trayIconProp = TypeDescriptor.GetProperties(_mainDesigner.Component)["TrayLargeIcon"];
            trayIconProp?.SetValue(_mainDesigner.Component, !ShowLargeIcons);
        }
        finally
        {
            t?.Commit();
        }
    }

    private void OnMenuLineupIcons(object sender, EventArgs e)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        DesignerTransaction t = null;
        try
        {
            t = host.CreateTransaction(SR.TrayLineUpIcons);
            DoLineupIcons();
        }
        finally
        {
            t?.Commit();
        }
    }

    private void DoLineupIcons()
    {
        if (_autoArrange)
        {
            return;
        }

        bool oldValue = _autoArrange;
        _autoArrange = true;
        try
        {
            DoAutoArrange(true);
        }
        finally
        {
            _autoArrange = oldValue;
        }
    }

    private void DoAutoArrange(bool dirtyDesigner)
    {
        if (_controls is null || _controls.Count <= 0)
        {
            return;
        }

        _controls.Sort(new AutoArrangeComparer());

        SuspendLayout();

        // Reset the autoscroll position before auto arranging.
        // This way, when OnLayout gets fired after this, we won't
        // have to move every component again.  Note that syncing
        // the selection will automatically select & scroll into view
        // the right components
        AutoScrollPosition = new Point(0, 0);

        try
        {
            Control prevCtl = null;
            bool positionedGlobal = true;
            foreach (Control ctl in _controls)
            {
                if (!ctl.Visible)
                {
                    continue;
                }

                // If we're auto arranging, always move the control.  If not, move the control only if it was never given a position.  This auto arranges it until the user messes with it, or until its position is saved into the resx. (if one control is no longer positioned, move all the other one as  we don't want them to go under one another)
                if (_autoArrange)
                {
                    PositionInNextAutoSlot(ctl as TrayControl, prevCtl, dirtyDesigner);
                }
                else if (!((TrayControl)ctl).Positioned || !positionedGlobal)
                {
                    PositionInNextAutoSlot(ctl as TrayControl, prevCtl, false);
                    positionedGlobal = false;
                }

                prevCtl = ctl;
            }

            _selectionUISvc?.SyncSelection();
        }
        finally
        {
            ResumeLayout();
        }
    }

    private void OnMenuArrangeIcons(object sender, EventArgs e)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        DesignerTransaction t = null;

        try
        {
            t = host.CreateTransaction(SR.TrayAutoArrange);

            PropertyDescriptor trayAAProp = TypeDescriptor.GetProperties(_mainDesigner.Component)["TrayAutoArrange"];
            trayAAProp?.SetValue(_mainDesigner.Component, !AutoArrange);
        }
        finally
        {
            t?.Commit();
        }
    }

    public bool AutoArrange
    {
        get => _autoArrange;
        set
        {
            if (_autoArrange != value)
            {
                _autoArrange = value;
                _menucmdArrangeIcons.Checked = value;

                if (_autoArrange)
                {
                    DoAutoArrange(true);
                }
            }
        }
    }

    /// <summary>
    ///  Gets the number of components contained within this tray.
    /// </summary>
    public int ComponentCount
    {
        get => Controls.Count;
    }

    internal GlyphCollection SelectionGlyphs
    {
        get
        {
            if (_glyphManager is not null)
            {
                return _glyphManager.SelectionGlyphs;
            }
            else
            {
                return null;
            }
        }
    }

    /// <summary>
    ///  Determines whether the tray will show large icon view or not.
    /// </summary>
    public bool ShowLargeIcons
    {
        get => _showLargeIcons;
        set
        {
            if (_showLargeIcons != value)
            {
                _showLargeIcons = value;
                _menucmdLargeIcons.Checked = ShowLargeIcons;

                ResetTrayControls();
                Invalidate(true);
            }
        }
    }

    bool IExtenderProvider.CanExtend(object extendee)
    {
        return (extendee is IComponent comp) && (TrayControl.FromComponent(comp) is not null);
    }

    IComponent IOleDragClient.Component
    {
        get => _mainDesigner.Component;
    }

    bool IOleDragClient.CanModifyComponents
    {
        get => true;
    }

    bool IOleDragClient.AddComponent(IComponent component, string name, bool firstAdd)
    {
        // the designer for controls decides what to do here
        if (_mainDesigner is IOleDragClient oleDragClient)
        {
            try
            {
                oleDragClient.AddComponent(component, name, firstAdd);
                PositionControl(TrayControl.FromComponent(component));
                _mouseDropLocation = InvalidPoint;
                return true;
            }
            catch
            {
            }
        }
        else
        {
            // for webforms (98109) just add the component directly to the host
            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            try
            {
                if (host is not null && host.Container is not null)
                {
                    if (host.Container.Components[name] is not null)
                    {
                        name = null;
                    }

                    host.Container.Add(component, name);
                    return true;
                }
            }
            catch
            {
            }
        }

        Debug.Fail("Don't know how to add component!");
        return false;
    }

    bool IOleDragClient.IsDropOk(IComponent component) => true;

    Control IOleDragClient.GetDesignerControl() => this;

    Control IOleDragClient.GetControlForComponent(object component)
    {
        if (component is IComponent comp)
        {
            return TrayControl.FromComponent(comp);
        }

        Debug.Fail("component is not IComponent");
        return null;
    }

    bool ISelectionUIHandler.BeginDrag(object[] components, SelectionRules rules, int initialX, int initialY)
    {
        if (TabOrderActive)
        {
            return false;
        }

        bool result = DragHandler.BeginDrag(components, rules, initialX, initialY);
        if (result)
        {
            if (!GetOleDragHandler().DoBeginDrag(components, rules, initialX, initialY))
            {
                return false;
            }
        }

        return result;
    }

    internal virtual OleDragDropHandler GetOleDragHandler()
    {
        _oleDragDropHandler ??= new TrayOleDragDropHandler(DragHandler, _serviceProvider, this);

        return _oleDragDropHandler;
    }

    internal virtual SelectionUIHandler DragHandler
    {
        get
        {
            _dragHandler ??= new TraySelectionUIHandler(this);

            return _dragHandler;
        }
    }

    void ISelectionUIHandler.DragMoved(object[] components, Rectangle offset) => DragHandler.DragMoved(components, offset);

    void ISelectionUIHandler.EndDrag(object[] components, bool cancel)
    {
        DragHandler.EndDrag(components, cancel);
        GetOleDragHandler().DoEndDrag();
        // Here, after the drag is finished and after we have resumed layout, adjust the location of the components we dragged by the scroll offset
        if (!_autoScrollPosBeforeDragging.IsEmpty)
        {
            foreach (IComponent comp in components)
            {
                TrayControl tc = TrayControl.FromComponent(comp);
                if (tc is not null)
                {
                    SetTrayLocation(comp, new Point(tc.Location.X - _autoScrollPosBeforeDragging.X, tc.Location.Y - _autoScrollPosBeforeDragging.Y));
                }
            }

            AutoScrollPosition = new Point(-_autoScrollPosBeforeDragging.X, -_autoScrollPosBeforeDragging.Y);
        }
    }

    // We render the selection UI glyph ourselves.
    Rectangle ISelectionUIHandler.GetComponentBounds(object component) => Rectangle.Empty;

    SelectionRules ISelectionUIHandler.GetComponentRules(object component) => SelectionRules.Visible | SelectionRules.Moveable;

    Rectangle ISelectionUIHandler.GetSelectionClipRect(object component)
    {
        if (IsHandleCreated)
        {
            return RectangleToScreen(ClientRectangle);
        }

        return Rectangle.Empty;
    }

    void ISelectionUIHandler.OnSelectionDoubleClick(IComponent component)
    {
        if (!TabOrderActive)
        {
            if (((IOleDragClient)this).GetControlForComponent(component) is TrayControl tc)
            {
                tc.ViewDefaultEvent(component);
            }
        }
    }

    bool ISelectionUIHandler.QueryBeginDrag(object[] components, SelectionRules rules, int initialX, int initialY)
        => DragHandler.QueryBeginDrag(components);

    void ISelectionUIHandler.ShowContextMenu(IComponent component) => OnContextMenu(MousePosition);

    private void OnContextMenu(Point location)
    {
        if (TabOrderActive)
        {
            return;
        }

        Capture = false;
        IMenuCommandService mcs = MenuService;
        if (mcs is not null)
        {
            Capture = false;
            Cursor.Clip = Rectangle.Empty;
            ISelectionService selectionService = (ISelectionService)GetService(typeof(ISelectionService));
            if (selectionService is not null
                && !(selectionService.SelectionCount == 1 && selectionService.PrimarySelection == _mainDesigner.Component))
            {
                mcs.ShowContextMenu(MenuCommands.TraySelectionMenu, location.X, location.Y);
            }
            else
            {
                mcs.ShowContextMenu(MenuCommands.ComponentTrayMenu, location.X, location.Y);
            }
        }
    }

    void ISelectionUIHandler.OleDragEnter(DragEventArgs de) => GetOleDragHandler().DoOleDragEnter(de);

    void ISelectionUIHandler.OleDragDrop(DragEventArgs de) => GetOleDragHandler().DoOleDragDrop(de);

    void ISelectionUIHandler.OleDragOver(DragEventArgs de) => GetOleDragHandler().DoOleDragOver(de);

    void ISelectionUIHandler.OleDragLeave() => GetOleDragHandler().DoOleDragLeave();

    /// <summary>
    ///  Adds a component to the tray.
    /// </summary>
    public virtual void AddComponent(IComponent component)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        // Ignore components that cannot be added to the tray
        if (!CanDisplayComponent(component))
        {
            return;
        }

        // And designate us as the selection UI handler for the control.
        if (_selectionUISvc is null)
        {
            _selectionUISvc = (ISelectionUIService)GetService(typeof(ISelectionUIService));

            // If there is no selection service, then we will provide our own.
            if (_selectionUISvc is null)
            {
                _selectionUISvc = new SelectionUIService(host);
                host.AddService(_selectionUISvc);
            }

            _grabHandle = _selectionUISvc.GetAdornmentDimensions(AdornmentType.GrabHandle);
        }

        // Create a new instance of a tray control.
        TrayControl trayctl = new(this, component);
        SuspendLayout();
        try
        {
            // Add it to us.
            Controls.Add(trayctl);
            _controls.Add(trayctl);
            // CanExtend can actually be called BEFORE the component is added to the ComponentTray. ToolStrip is such as scenario:
            // 1. Add a timer to the Tray.
            // 2. Add a ToolStrip.
            // 3. ToolStripDesigner.Initialize will be called before ComponentTray.AddComponent, so the ToolStrip is not yet added to the tray.
            // 4. TooStripDesigner.Initialize calls GetProperties, which causes our CanExtend to be called.
            // 5. CanExtend will return false, since the component has not yet been added.
            // 6. This causes all sorts of badness
            // Fix is to refresh.
            TypeDescriptor.Refresh(component);
            if (host is not null && !host.Loading)
            {
                PositionControl(trayctl);
            }

            _selectionUISvc?.AssignSelectionUIHandler(component, this);

            InheritanceAttribute attr = trayctl.InheritanceAttribute;
            if (attr.InheritanceLevel != InheritanceLevel.NotInherited)
            {
                InheritanceUI iui = InheritanceUI;
                iui?.AddInheritedControl(trayctl, attr.InheritanceLevel);
            }
        }
        finally
        {
            ResumeLayout();
        }

        if (host is not null && !host.Loading)
        {
            ScrollControlIntoView(trayctl);
        }
    }

    [CLSCompliant(false)]
    protected virtual bool CanCreateComponentFromTool(ToolboxItem tool)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        Debug.Assert(host is not null, "Service object could not provide us with a designer host.");
        // Disallow controls to be added to the component tray.
        Type compType = host.GetType(tool.TypeName);
        if (compType is null)
        {
            return true;
        }

        if (!compType.IsSubclassOf(typeof(Control)))
        {
            return true;
        }

        Type designerType = GetDesignerType(compType, typeof(IDesigner));
        return !(typeof(ControlDesigner).IsAssignableFrom(designerType));
    }

    private Type GetDesignerType(Type t, Type designerBaseType)
    {
        Type designerType = null;
        // Get the set of attributes for this type
        AttributeCollection attributes = TypeDescriptor.GetAttributes(t);
        for (int i = 0; i < attributes.Count; i++)
        {
            if (attributes[i] is DesignerAttribute da)
            {
                Type attributeBaseType = Type.GetType(da.DesignerBaseTypeName);
                if (attributeBaseType is not null && attributeBaseType == designerBaseType)
                {
                    bool foundService = false;
                    ITypeResolutionService tr = (ITypeResolutionService)GetService(typeof(ITypeResolutionService));
                    if (tr is not null)
                    {
                        foundService = true;
                        designerType = tr.GetType(da.DesignerTypeName);
                    }

                    if (!foundService)
                    {
                        designerType = Type.GetType(da.DesignerTypeName);
                    }

                    if (designerType is not null)
                    {
                        break;
                    }
                }
            }
        }

        return designerType;
    }

    /// <summary>
    ///  This method determines if a UI representation for the given component should be provided.
    ///  If it returns true, then the component will get a glyph in the tray area.  If it returns
    ///  false, then the component will not actually be added to the tray.  The default
    ///  implementation looks for DesignTimeVisibleAttribute.Yes on the component's class.
    /// </summary>
    protected virtual bool CanDisplayComponent(IComponent component)
    {
        return TypeDescriptor.GetAttributes(component).Contains(DesignTimeVisibleAttribute.Yes);
    }

    [CLSCompliant(false)]
    public void CreateComponentFromTool(ToolboxItem tool)
    {
        if (!CanCreateComponentFromTool(tool))
        {
            return;
        }

        // We invoke the drag drop handler for this.  This implementation is shared between all designers that create components.
        GetOleDragHandler().CreateTool(tool, null, 0, 0, 0, 0, false, false);
    }

    /// <summary>
    ///  Displays the given exception to the user.
    /// </summary>
    protected void DisplayError(Exception e)
    {
        IUIService uis = (IUIService)GetService(typeof(IUIService));
        if (uis is not null)
        {
            uis.ShowError(e);
        }
        else
        {
            string message = e.Message;
            if (message is null || message.Length == 0)
            {
                message = e.ToString();
            }

            RTLAwareMessageBox.Show(null, message, null, MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, 0);
        }
    }

    /// <summary>
    ///  Disposes of the resources (other than memory) used by the component tray object.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (disposing && _controls is not null)
        {
            IExtenderProviderService es = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));
            es?.RemoveExtenderProvider(this);

            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            if (_eventHandlerService is not null)
            {
                if (host is not null)
                {
                    host.RemoveService<IEventHandlerService>();
                    _eventHandlerService = null;
                }
            }

            IComponentChangeService componentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
            if (componentChangeService is not null)
            {
                componentChangeService.ComponentRemoved -= OnComponentRemoved;
            }

            SystemEvents.DisplaySettingsChanged -= OnSystemSettingChanged;
            SystemEvents.InstalledFontsChanged -= OnSystemSettingChanged;
            SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
            IMenuCommandService mcs = MenuService;
            if (mcs is not null)
            {
                Debug.Assert(_menucmdArrangeIcons is not null, "Null Menu Command for ArrangeIcons");
                Debug.Assert(_menucmdLineupIcons is not null, "Null Menu Command for LineupIcons");
                Debug.Assert(_menucmdLargeIcons is not null, "Null Menu Command for LargeIcons");
                mcs.RemoveCommand(_menucmdArrangeIcons);
                mcs.RemoveCommand(_menucmdLineupIcons);
                mcs.RemoveCommand(_menucmdLargeIcons);
            }

            _selectionUISvc = null;

            if (_inheritanceUI is not null)
            {
                _inheritanceUI.Dispose();
                _inheritanceUI = null;
            }

            _serviceProvider = null;
            _controls.Clear();
            _controls = null;

            if (_glyphManager is not null)
            {
                _glyphManager.Dispose();
                _glyphManager = null;
            }
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  Similar to GetNextControl on Control, this method returns the next
    ///  component in the tray, given a starting component.  It will return
    ///  null if the end (or beginning, if forward is false) of the list
    ///  is encountered.
    /// </summary>
    public IComponent GetNextComponent(IComponent component, bool forward)
    {
        for (int i = 0; i < _controls.Count; i++)
        {
            TrayControl control = (TrayControl)_controls[i];
            if (control.Component == component)
            {
                int targetIndex = (forward ? i + 1 : i - 1);
                if (targetIndex >= 0 && targetIndex < _controls.Count)
                {
                    return ((TrayControl)_controls[targetIndex]).Component;
                }

                // Reached the end of the road.
                return null;
            }
        }

        // If we got here then the component isn't in our list.  Prime the caller with either the first or the last.
        if (_controls.Count > 0)
        {
            int targetIndex = (forward ? 0 : _controls.Count - 1);
            return ((TrayControl)_controls[targetIndex]).Component;
        }

        return null;
    }

    /// <summary>
    ///  Accessor method for the location extender property.  We offer this extender
    ///  to all non-visual components.
    /// </summary>
    [Category("Layout")]
    [Localizable(false)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [SRDescription("ControlLocationDescr")]
    [DesignOnly(true)]
    public Point GetLocation(IComponent receiver)
    {
        PropertyDescriptor loc = TypeDescriptor.GetProperties(receiver.GetType())["Location"];
        if (loc is not null)
        {
            // In this case the component already had a Location property, and what the caller wants is the underlying components Location, not the tray location. Why? Because we now use TrayLocation.
            return (Point)(loc.GetValue(receiver));
        }
        else
        {
            // If the component didn't already have a Location property, then the caller really wants the tray location. Could be a 3rd party vendor.
            return GetTrayLocation(receiver);
        }
    }

    /// <summary>
    ///  Accessor method for the location extender property.  We offer this extender
    ///  to all non-visual components.
    /// </summary>
    [Category("Layout")]
    [Localizable(false)]
    [Browsable(false)]
    [SRDescription("ControlLocationDescr")]
    [DesignOnly(true)]
    public Point GetTrayLocation(IComponent receiver)
    {
        Control c = TrayControl.FromComponent(receiver);
        if (c is null)
        {
            Debug.Fail("Anything we're extending should have a component view.");
            return default;
        }

        Point loc = c.Location;
        Point autoScrollLoc = AutoScrollPosition;
        return new Point(loc.X - autoScrollLoc.X, loc.Y - autoScrollLoc.Y);
    }

    /// <summary>
    ///  Gets the requested service type.
    /// </summary>
    protected override object GetService(Type serviceType)
    {
        object service = null;
        Debug.Assert(_serviceProvider is not null, "Trying to access services too late or too early.");
        if (_serviceProvider is not null)
        {
            service = _serviceProvider.GetService(serviceType);
        }

        return service;
    }

    /// <summary>
    ///  Returns true if the given component is being shown on the tray.
    /// </summary>
    public bool IsTrayComponent(IComponent comp)
    {
        if (TrayControl.FromComponent(comp) is null)
        {
            return false;
        }

        foreach (Control control in Controls)
        {
            if (control is TrayControl tc && tc.Component == comp)
            {
                return true;
            }
        }

        return false;
    }

    protected override void OnMouseDoubleClick(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseDoubleClick(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        base.OnDoubleClick(e);
        if (!TabOrderActive)
        {
            OnLostCapture();
            IEventBindingService eps = (IEventBindingService)GetService(typeof(IEventBindingService));
            eps?.ShowCode();
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onGiveFeedback to send this event to any registered event listeners.
    /// </summary>
    protected override void OnGiveFeedback(GiveFeedbackEventArgs gfevent)
    {
        base.OnGiveFeedback(gfevent);
        GetOleDragHandler().DoOleGiveFeedback(gfevent);
    }

    /// <summary>
    ///  Called in response to a drag drop for OLE drag and drop.  Here we
    ///  drop a toolbox component on our parent control.
    /// </summary>
    protected override void OnDragDrop(DragEventArgs de)
    {
        // This will be used once during PositionComponent to place the component at the drop point. It is automatically
        // set to null afterwards, so further components appear after the first one dropped.
        _mouseDropLocation = PointToClient(new Point(de.X, de.Y));
        _autoScrollPosBeforeDragging = AutoScrollPosition;

        if (_mouseDragTool is not null)
        {
            ToolboxItem tool = _mouseDragTool;
            _mouseDragTool = null;
            try
            {
                IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
                IDesigner designer = host.GetDesigner(host.RootComponent);
                if (designer is IToolboxUser itu)
                {
                    itu.ToolPicked(tool);
                }
                else
                {
                    CreateComponentFromTool(tool);
                }
            }
            catch (Exception e)
            {
                DisplayError(e);
                if (e.IsCriticalException())
                {
                    throw;
                }
            }

            de.Effect = DragDropEffects.Copy;
        }
        else
        {
            GetOleDragHandler().DoOleDragDrop(de);
        }

        _mouseDropLocation = InvalidPoint;
        ResumeLayout();
    }

    /// <summary>
    ///  Called in response to a drag enter for OLE drag and drop.
    /// </summary>
    protected override void OnDragEnter(DragEventArgs de)
    {
        if (!TabOrderActive)
        {
            SuspendLayout();
            _toolboxService ??= (IToolboxService)GetService(typeof(IToolboxService));

            OleDragDropHandler dragDropHandler = GetOleDragHandler();
            object[] dragComps = OleDragDropHandler.GetDraggingObjects(de);
            // Only assume the items came from the ToolBox if dragComps is null
            if (_toolboxService is not null && dragComps is null)
            {
                _mouseDragTool = _toolboxService.DeserializeToolboxItem(de.Data, (IDesignerHost)GetService(typeof(IDesignerHost)));
            }

            if (_mouseDragTool is not null)
            {
                Debug.Assert((de.AllowedEffect & (DragDropEffects.Move | DragDropEffects.Copy)) != 0, "DragDropEffect.Move | .Copy isn't allowed?");
                if ((de.AllowedEffect & DragDropEffects.Move) != 0)
                {
                    de.Effect = DragDropEffects.Move;
                }
                else
                {
                    de.Effect = DragDropEffects.Copy;
                }
            }
            else
            {
                dragDropHandler.DoOleDragEnter(de);
            }
        }
    }

    /// <summary>
    ///  Called when a drag-drop operation leaves the control designer view
    /// </summary>
    protected override void OnDragLeave(EventArgs e)
    {
        _mouseDragTool = null;
        GetOleDragHandler().DoOleDragLeave();
        ResumeLayout();
    }

    /// <summary>
    ///  Called when a drag drop object is dragged over the control designer view
    /// </summary>
    protected override void OnDragOver(DragEventArgs de)
    {
        if (_mouseDragTool is not null)
        {
            Debug.Assert((de.AllowedEffect & DragDropEffects.Copy) != 0, "DragDropEffect.Move isn't allowed?");
            de.Effect = DragDropEffects.Copy;
        }
        else
        {
            GetOleDragHandler().DoOleDragOver(de);
        }
    }

    /// <summary>
    ///  Forces the layout of any docked or anchored child controls.
    /// </summary>
    protected override void OnLayout(LayoutEventArgs levent)
    {
        DoAutoArrange(false);
        // make sure selection service redraws
        Invalidate(true);
        base.OnLayout(levent);
    }

    /// <summary>
    ///  This is called when we lose capture.  Here we get rid of any
    ///  rubber band we were drawing.  You should put any cleanup
    ///  code in here.
    /// </summary>
    protected virtual void OnLostCapture()
    {
        if (_mouseDragStart != InvalidPoint)
        {
            Cursor.Clip = Rectangle.Empty;
            if (_mouseDragEnd != InvalidPoint)
            {
                DrawRubber(_mouseDragStart, _mouseDragEnd);
                _mouseDragEnd = InvalidPoint;
            }

            _mouseDragStart = InvalidPoint;
        }
    }

    private void DrawRubber(Point start, Point end)
    {
        _mouseDragWorkspace.X = Math.Min(start.X, end.X);
        _mouseDragWorkspace.Y = Math.Min(start.Y, end.Y);
        _mouseDragWorkspace.Width = Math.Abs(end.X - start.X);
        _mouseDragWorkspace.Height = Math.Abs(end.Y - start.Y);
        _mouseDragWorkspace = RectangleToScreen(_mouseDragWorkspace);
        ControlPaint.DrawReversibleFrame(_mouseDragWorkspace, BackColor, FrameStyle.Dashed);
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onMouseDown to send this event to any registered event listeners.
    /// </summary>
    protected override void OnMouseDown(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseDown(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        base.OnMouseDown(e);
        if (!TabOrderActive)
        {
            _toolboxService ??= (IToolboxService)GetService(typeof(IToolboxService));

            FocusDesigner();
            if (e.Button == MouseButtons.Left && _toolboxService is not null)
            {
                ToolboxItem tool = _toolboxService.GetSelectedToolboxItem((IDesignerHost)GetService(typeof(IDesignerHost)));
                if (tool is not null)
                {
                    // mouseDropLocation is checked in PositionControl, which should get called as a result of adding a new component.  This allows us to set the position without flickering, while still providing support for auto layout if the control was double clicked or added through extensibility.
                    _mouseDropLocation = new Point(e.X, e.Y);
                    try
                    {
                        CreateComponentFromTool(tool);
                        _toolboxService.SelectedToolboxItemUsed();
                    }
                    catch (Exception ex)
                    {
                        DisplayError(ex);
                        if (ex.IsCriticalException())
                        {
                            throw;
                        }
                    }

                    _mouseDropLocation = InvalidPoint;
                    return;
                }
            }

            // If it is the left button, start a rubber band drag to lasso controls.
            if (e.Button == MouseButtons.Left)
            {
                _mouseDragStart = new Point(e.X, e.Y);
                Capture = true;
                Cursor.Clip = RectangleToScreen(ClientRectangle);
            }
            else
            {
                try
                {
                    ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService));
                    ss?.SetSelectedComponents(new object[] { _mainDesigner.Component });
                }
                catch (Exception ex) when (!ex.IsCriticalException())
                {
                    // Nothing we can really do here; just eat it.
                }
            }
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onMouseMove to send this event to any registered event listeners.
    /// </summary>
    protected override void OnMouseMove(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseMove(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        base.OnMouseMove(e);

        // If we are dragging, then draw our little rubber band.
        if (_mouseDragStart != InvalidPoint)
        {
            if (_mouseDragEnd != InvalidPoint)
            {
                DrawRubber(_mouseDragStart, _mouseDragEnd);
            }
            else
            {
                _mouseDragEnd = new Point(0, 0);
            }

            _mouseDragEnd.X = e.X;
            _mouseDragEnd.Y = e.Y;
            DrawRubber(_mouseDragStart, _mouseDragEnd);
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onMouseUp to send this event to any registered event listeners.
    /// </summary>
    protected override void OnMouseUp(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseUp(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        if (_mouseDragStart != InvalidPoint && e.Button == MouseButtons.Left)
        {
            IComponent[] comps;
            Capture = false;
            Cursor.Clip = Rectangle.Empty;
            if (_mouseDragEnd != InvalidPoint)
            {
                DrawRubber(_mouseDragStart, _mouseDragEnd);
                Rectangle rect = new Rectangle
                {
                    X = Math.Min(_mouseDragStart.X, e.X),
                    Y = Math.Min(_mouseDragStart.Y, e.Y),
                    Width = Math.Abs(e.X - _mouseDragStart.X),
                    Height = Math.Abs(e.Y - _mouseDragStart.Y)
                };
                comps = GetComponentsInRect(rect);
                _mouseDragEnd = InvalidPoint;
            }
            else
            {
                comps = [];
            }

            if (comps.Length == 0)
            {
                comps = [_mainDesigner.Component];
            }

            try
            {
                ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService));
                ss?.SetSelectedComponents(comps);
            }
            catch (Exception ex) when (!ex.IsCriticalException())
            {
                // Nothing we can really do here; just eat it.
            }

            _mouseDragStart = InvalidPoint;
        }

        base.OnMouseUp(e);
    }

    private IComponent[] GetComponentsInRect(Rectangle rect)
    {
        List<IComponent> list = [];
        int controlCount = Controls.Count;
        for (int i = 0; i < controlCount; i++)
        {
            Control child = Controls[i];
            Rectangle bounds = child.Bounds;
            if (child is TrayControl tc && bounds.IntersectsWith(rect))
            {
                list.Add(tc.Component);
            }
        }

        return [.. list];
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        if (_fResetAmbient || _fSelectionChanged)
        {
            _fResetAmbient = false;
            _fSelectionChanged = false;
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            if (uiService is not null)
            {
                Color styleColor;
                if (uiService.Styles["ArtboardBackground"] is Color backgroundColor)
                {
                    styleColor = backgroundColor;
                }

                // Can't use 'as' here since Color is a value type
                else if (uiService.Styles["VsColorDesignerTray"] is Color trayColor)
                {
                    styleColor = trayColor;
                }
                else if (uiService.Styles["HighlightColor"] is Color highlightColor)
                {
                    // Since v1, we have had code here that checks for HighlightColor, so some hosts (like WinRes) have been setting it. If VsColorDesignerTray isn't present, we look for HighlightColor for backward compat.
                    styleColor = highlightColor;
                }
                else
                {
                    // No style color provided? Let's pick a default.
                    styleColor = SystemColors.Info;
                }

                BackColor = styleColor;
                Font = (Font)uiService.Styles["DialogFont"];
                foreach (Control ctl in _controls)
                {
                    ctl.BackColor = styleColor;
                    ctl.ForeColor = ForeColor;
                }
            }
        }

        base.OnPaint(pe);
        Graphics gr = pe.Graphics;
        // Now, if we have a selection, paint it
        if (_selectedObjects is not null)
        {
            bool first = true; // indicates the first iteration of our foreach loop
            HatchBrush selectionBorderBrush;
            if (SystemInformation.HighContrast)
            {
                selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.HighlightText, Color.Transparent);
            }
            else
            {
                selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.ControlDarkDark, Color.Transparent);
            }

            try
            {
                foreach (object o in _selectedObjects)
                {
                    Control c = ((IOleDragClient)this).GetControlForComponent(o);
                    if (c is not null && c.Visible)
                    {
                        Rectangle innerRect = c.Bounds;
                        if (SystemInformation.HighContrast)
                        {
                            c.ForeColor = SystemColors.HighlightText;
                            c.BackColor = SystemColors.Highlight;
                        }

                        NoResizeHandleGlyph glyph = new(innerRect, SelectionRules.None, first, null);
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Top));
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Bottom));
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Left));
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Right));
                        // Need to draw this one last
                        DesignerUtils.DrawNoResizeHandle(gr, glyph.Bounds, first);
                    }

                    first = false;
                }
            }
            finally
            {
                selectionBorderBrush?.Dispose();
            }
        }

        // paint any glyphs
        _glyphManager?.OnPaintGlyphs(pe);
    }

    /// <summary>
    ///  Sets the cursor.  You may override this to set your own
    ///  cursor.
    /// </summary>
    protected virtual void OnSetCursor()
    {
        _toolboxService ??= (IToolboxService)GetService(typeof(IToolboxService));

        if (_toolboxService is null || !_toolboxService.SetCursor())
        {
            Cursor.Current = Cursors.Default;
        }
    }

    /// <summary>
    ///  Removes a component from the tray.
    /// </summary>
    public virtual void RemoveComponent(IComponent component)
    {
        TrayControl c = TrayControl.FromComponent(component);
        if (c is not null)
        {
            try
            {
                InheritanceAttribute attr = c.InheritanceAttribute;
                if (attr.InheritanceLevel != InheritanceLevel.NotInherited && _inheritanceUI is not null)
                {
                    _inheritanceUI.RemoveInheritedControl(c);
                }

                _controls?.Remove(c);
            }
            finally
            {
                c.Dispose();
            }
        }
    }

    /// <summary>
    ///  Accessor method for the location extender property.  We offer this extender
    ///  to all non-visual components.
    /// </summary>
    public void SetLocation(IComponent receiver, Point location)
    {
        // This really should only be called when we are loading.
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        if (host is not null && host.Loading)
        {
            // If we are loading, and we get called here, that's because we have provided the extended Location property. In this case we are loading an old project, and what we are really setting is the tray location.
            SetTrayLocation(receiver, location);
        }
        else
        {
            // we are not loading
            PropertyDescriptor loc = TypeDescriptor.GetProperties(receiver.GetType())["Location"];
            if (loc is not null)
            {
                // so if the component already had the Location property, what the caller wants is really the underlying component's Location property.
                loc.SetValue(receiver, location);
            }
            else
            {
                // if the component didn't have a Location property, then the caller really wanted the tray location.
                SetTrayLocation(receiver, location);
            }
        }
    }

    /// <summary>
    ///  Accessor method for the location extender property.  We offer this extender
    ///  to all non-visual components.
    /// </summary>
    public void SetTrayLocation(IComponent receiver, Point location)
    {
        TrayControl c = TrayControl.FromComponent(receiver);
        if (c is null)
        {
            Debug.Fail("Anything we're extending should have a component view.");
            return;
        }

        if (c.Parent == this)
        {
            Point autoScrollLoc = AutoScrollPosition;
            location = new Point(location.X + autoScrollLoc.X, location.Y + autoScrollLoc.Y);
            if (c.Visible)
            {
                RearrangeInAutoSlots(c, location);
            }
        }
        else if (!c.Location.Equals(location))
        {
            c.Location = location;
            c.Positioned = true;
        }
    }

    /// <summary>
    ///  We override our base class's WndProc to monitor certain messages.
    /// </summary>
    protected override void WndProc(ref Message m)
    {
        switch (m.MsgInternal)
        {
            case PInvoke.WM_CANCELMODE:
                // When we get cancelmode (i.e. you tabbed away to another window) then we want to cancel
                // any pending drag operation.
                OnLostCapture();
                break;
            case PInvoke.WM_SETCURSOR:
                OnSetCursor();
                return;
            case PInvoke.WM_HSCROLL:
            case PInvoke.WM_VSCROLL:
                // When we scroll, we reposition a control without causing a property change event.
                // Therefore, we must tell the selection UI service to sync itself.
                base.WndProc(ref m);
                _selectionUISvc?.SyncSelection();

                return;
            case PInvoke.WM_STYLECHANGED:
                // When the scroll bars first appear, we need to invalidate so we properly paint our grid.
                Invalidate();
                break;
            case PInvoke.WM_CONTEXTMENU:
                {
                    // Pop a context menu for the composition designer.
                    Point location = PARAM.ToPoint(m.LParamInternal);
                    if (location.X == -1 && location.Y == -1)
                    {
                        // For shift-F10.
                        location = MousePosition;
                    }

                    OnContextMenu(location);
                    break;
                }

            case PInvoke.WM_NCHITTEST:
                {
                    if (_glyphManager is not null)
                    {
                        // Get a hit test on any glyphs that we are managing this way.
                        // We know where to route appropriate messages.
                        Point location = PARAM.ToPoint(m.LParamInternal);
                        location.Offset(PointToClient(default));
                        _glyphManager.GetHitTest(location);
                    }

                    base.WndProc(ref m);
                    break;
                }

            default:
                base.WndProc(ref m);
                break;
        }
    }

    internal static TrayControl GetTrayControlFromComponent(IComponent comp)
    {
        return TrayControl.FromComponent(comp);
    }

    private bool TabOrderActive
    {
        get
        {
            if (!_queriedTabOrder)
            {
                _queriedTabOrder = true;
                IMenuCommandService mcs = MenuService;
                if (mcs is not null)
                {
                    _tabOrderCommand = mcs.FindCommand(StandardCommands.TabOrder);
                }
            }

            return _tabOrderCommand is not null && _tabOrderCommand.Checked;
        }
    }

    private InheritanceUI InheritanceUI
    {
        get
        {
            _inheritanceUI ??= new InheritanceUI();

            return _inheritanceUI;
        }
    }

    private IMenuCommandService MenuService
    {
        get
        {
            _menuCommandService ??= (IMenuCommandService)GetService(typeof(IMenuCommandService));

            return _menuCommandService;
        }
    }

    internal void FocusDesigner()
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        if (host is not null && host.RootComponent is not null)
        {
            if (host.GetDesigner(host.RootComponent) is IRootDesigner rd)
            {
                ViewTechnology[] techs = rd.SupportedTechnologies;
                if (techs.Length > 0)
                {
                    if (rd.GetView(techs[0]) is Control view)
                    {
                        view.Focus();
                    }
                }
            }
        }
    }

    internal Size ParentGridSize
    {
        get
        {
            if (_mainDesigner is ParentControlDesigner designer)
            {
                return designer.ParentGridSize;
            }

            return new Size(8, 8);
        }
    }

    internal void UpdatePastePositions(List<Control> components)
    {
        foreach (TrayControl c in components)
        {
            if (!CanDisplayComponent(c.Component))
            {
                return;
            }

            if (_mouseDropLocation == InvalidPoint)
            {
                Control prevCtl = null;
                if (_controls.Count > 1)
                {
                    prevCtl = _controls[^1];
                }

                PositionInNextAutoSlot(c, prevCtl, true);
            }
            else
            {
                PositionControl(c);
            }

            c.BringToFront();
        }
    }

    private void PositionControl(TrayControl c)
    {
        Debug.Assert(c.Visible, $"TrayControl for {c.Component} should not be positioned");
        if (!_autoArrange)
        {
            if (_mouseDropLocation != InvalidPoint)
            {
                if (!c.Location.Equals(_mouseDropLocation))
                {
                    c.Location = _mouseDropLocation;
                }
            }
            else
            {
                Control prevCtl = null;
                if (_controls.Count > 1)
                {
                    // PositionControl can be called when all the controls have been added (from IOleDragClient.AddComponent), so we can't use the old way of looking up the previous control (prevCtl = controls[controls.Count - 2]
                    int index = _controls.IndexOf(c);
                    Debug.Assert(index >= 1, "Got the wrong index, how could that be?");
                    if (index >= 1)
                    {
                        prevCtl = _controls[index - 1];
                    }
                }

                PositionInNextAutoSlot(c, prevCtl, true);
            }
        }
        else
        {
            if (_mouseDropLocation != InvalidPoint)
            {
                RearrangeInAutoSlots(c, _mouseDropLocation);
            }
            else
            {
                Control prevCtl = null;
                if (_controls.Count > 1)
                {
                    int index = _controls.IndexOf(c);
                    Debug.Assert(index >= 1, "Got the wrong index, how could that be?");
                    if (index >= 1)
                    {
                        prevCtl = _controls[index - 1];
                    }
                }

                PositionInNextAutoSlot(c, prevCtl, true);
            }
        }
    }

    internal void RearrangeInAutoSlots(Control c, Point pos)
    {
        Debug.Assert(_controls.IndexOf(c) != -1, "Add control to the list of controls before autoarranging.!!!");
        Debug.Assert(Visible == c.Visible, $"TrayControl for {((TrayControl)c).Component} should not be positioned");

        TrayControl tc = (TrayControl)c;
        tc.Positioned = true;
        tc.Location = pos;
    }

    private bool PositionInNextAutoSlot(TrayControl c, Control prevCtl, bool dirtyDesigner)
    {
        Debug.Assert(c.Visible, $"TrayControl for {c.Component} should not be positioned");
        if (_whiteSpace.IsEmpty)
        {
            Debug.Assert(_selectionUISvc is not null, "No SelectionUIService available for tray.");
            _whiteSpace = new Point(_selectionUISvc.GetAdornmentDimensions(AdornmentType.GrabHandle));
            _whiteSpace.X = _whiteSpace.X * 2 + 3;
            _whiteSpace.Y = _whiteSpace.Y * 2 + 3;
        }

        if (prevCtl is null)
        {
            Rectangle display = DisplayRectangle;
            Point newLoc = new(display.X + _whiteSpace.X, display.Y + _whiteSpace.Y);
            if (!c.Location.Equals(newLoc))
            {
                c.Location = newLoc;
                if (dirtyDesigner)
                {
                    IComponent comp = c.Component;
                    Debug.Assert(comp is not null, "Component for the TrayControl is null");
                    PropertyDescriptor ctlLocation = TypeDescriptor.GetProperties(comp)["TrayLocation"];
                    if (ctlLocation is not null)
                    {
                        Point autoScrollLoc = AutoScrollPosition;
                        newLoc = new Point(newLoc.X - autoScrollLoc.X, newLoc.Y - autoScrollLoc.Y);
                        ctlLocation.SetValue(comp, newLoc);
                    }
                }
                else
                {
                    c.Location = newLoc;
                }

                return true;
            }
        }
        else
        {
            // Calculate the next location for this control.
            Rectangle bounds = prevCtl.Bounds;
            Point newLoc = new(bounds.X + bounds.Width + _whiteSpace.X, bounds.Y);

            // Check to see if it goes over the edge of our window.  If it does, then wrap it.
            if (newLoc.X + c.Size.Width > Size.Width)
            {
                newLoc.X = _whiteSpace.X;
                newLoc.Y += bounds.Height + _whiteSpace.Y;
            }

            if (!c.Location.Equals(newLoc))
            {
                if (dirtyDesigner)
                {
                    IComponent comp = c.Component;
                    Debug.Assert(comp is not null, "Component for the TrayControl is null");
                    PropertyDescriptor ctlLocation = TypeDescriptor.GetProperties(comp)["TrayLocation"];
                    if (ctlLocation is not null)
                    {
                        Point autoScrollLoc = AutoScrollPosition;
                        newLoc = new Point(newLoc.X - autoScrollLoc.X, newLoc.Y - autoScrollLoc.Y);
                        ctlLocation.SetValue(comp, newLoc);
                    }
                }
                else
                {
                    c.Location = newLoc;
                }

                return true;
            }
        }

        return false;
    }

    internal class TrayControl : Control
    {
        // Values that define this tray control
        private readonly IComponent _component; // the component this control is representing
        private Image _toolboxBitmap; // the bitmap used to represent the component
        private int _cxIcon; // the dimensions of the bitmap
        private int _cyIcon; // the dimensions of the bitmap
        private readonly InheritanceAttribute _inheritanceAttribute;

        // Services that we use often enough to cache.
        private readonly ComponentTray _tray;
        // transient values that are used during mouse drags
        private Point _mouseDragLast = InvalidPoint; // the last position of the mouse during a drag.
        private bool _mouseDragMoved; // has the mouse been moved during this drag?
        private bool _ctrlSelect; // was the ctrl key down on the mouse down?
        private bool _positioned; // Have we given this control an explicit location yet?
        private const int WhiteSpace = 5;
        private readonly int _borderWidth;
        internal bool _fRecompute; // This flag tells the TrayControl that it needs to retrieve the font and the background color before painting.

        /// <summary>
        ///  Creates a new TrayControl based on the component.
        /// </summary>
        public TrayControl(ComponentTray tray, IComponent component)
        {
            _tray = tray;
            _component = component;
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.Selectable, false);
            _borderWidth = SystemInformation.BorderSize.Width;
            UpdateIconInfo();

            IComponentChangeService cs = (IComponentChangeService)tray.GetService(typeof(IComponentChangeService));
            if (cs is not null)
            {
                cs.ComponentRename += OnComponentRename;
            }

            ISite site = component.Site;
            string name = null;

            if (site is not null)
            {
                name = site.Name;
                IDictionaryService ds = (IDictionaryService)site.GetService(typeof(IDictionaryService));
                Debug.Assert(ds is not null, "ComponentTray relies on IDictionaryService, which is not available.");
                ds?.SetValue(GetType(), this);
            }

            // We always want name to have something in it, so we default to the class name.  This way the design instance contains something semi-intuitive if we don't have a site.
            name ??= component.GetType().Name;

            Text = name;
            _inheritanceAttribute = (InheritanceAttribute)TypeDescriptor.GetAttributes(component)[typeof(InheritanceAttribute)];
            TabStop = false;
        }

        /// <summary>
        ///  Retrieves the component this control is representing.
        /// </summary>
        public IComponent Component
        {
            get => _component;
        }

        public override Font Font
        {
            get => _tray.Font;
        }

        public InheritanceAttribute InheritanceAttribute
        {
            get => _inheritanceAttribute;
        }

        public bool Positioned
        {
            get => _positioned;
            set => _positioned = value;
        }

        /// <summary>
        ///  Adjusts the size of the control based on the contents.
        /// </summary>
        // CONSIDER: this method gets called three or four times per component, and is even reentrant (CreateGraphics can force handle creation, and OnCreateHandle calls this method).  There's probably a better way to do this, but since this doesn't seem to be on the critical path, I'm not going to lose sleep over it.
        private void AdjustSize()
        {
            // CONSIDER: this forces handle creation.  Can we delay this calculation?
            Graphics gr = CreateGraphics();
            try
            {
                Size sz = Size.Ceiling(gr.MeasureString(Text, Font));

                Rectangle rc = Bounds;

                if (_tray.ShowLargeIcons)
                {
                    rc.Width = Math.Max(_cxIcon, sz.Width) + 4 * _borderWidth + 2 * WhiteSpace;
                    rc.Height = _cyIcon + 2 * WhiteSpace + sz.Height + 4 * _borderWidth;
                }
                else
                {
                    rc.Width = _cxIcon + sz.Width + 4 * _borderWidth + 2 * WhiteSpace;
                    rc.Height = Math.Max(_cyIcon, sz.Height) + 4 * _borderWidth;
                }

                Bounds = rc;
                Invalidate();
            }

            finally
            {
                gr?.Dispose();
            }

            _tray._glyphManager?.UpdateLocation(this);
        }

        protected override AccessibleObject CreateAccessibilityInstance() => new TrayControlAccessibleObject(this, _tray);

        /// <summary>
        ///  Destroys this control. Views automatically destroy themselves when they are removed from the design container.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ISite site = _component.Site;
                if (site is not null)
                {
                    if (site.TryGetService(out IComponentChangeService cs))
                    {
                        cs.ComponentRename -= OnComponentRename;
                    }

                    if (site.TryGetService(out IDictionaryService ds))
                    {
                        ds.SetValue(typeof(TrayControl), null);
                    }
                }
            }

            base.Dispose(disposing);
        }

        /// <summary>
        ///  Retrieves the tray control object for the given component.
        /// </summary>
        public static TrayControl FromComponent(IComponent component)
        {
            if (component is null)
            {
                return null;
            }

            if (component.Site.TryGetService(out IDictionaryService ds))
            {
                return (TrayControl)ds.GetValue(typeof(TrayControl));
            }

            return null;
        }

        /// <summary>
        ///  Delegate that is called in response to a name change.  Here we update our own stashed version of the name, recalculate our size and repaint.
        /// </summary>
        private void OnComponentRename(object sender, ComponentRenameEventArgs e)
        {
            if (e.Component == _component)
            {
                Text = e.NewName;
                AdjustSize();
            }
        }

        /// <summary>
        ///  Overrides handle creation notification for a control.  Here we just ensure that we're the proper size.
        /// </summary>
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            AdjustSize();
        }

        /// <summary>
        ///  Called in response to a double-click of the left mouse button.  The default behavior here calls onDoubleClick on IMouseHandler
        /// </summary>
        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);
            if (!_tray.TabOrderActive)
            {
                IDesignerHost host = (IDesignerHost)_tray.GetService(typeof(IDesignerHost));
                Debug.Assert(host is not null, "Component tray does not have access to designer host.");
                if (host is not null)
                {
                    _mouseDragLast = InvalidPoint;
                    Capture = false;
                    // We try to get a designer for the component and let it view the event.  If this fails, then we'll try to do it ourselves.
                    IDesigner designer = host.GetDesigner(_component);
                    if (designer is null)
                    {
                        ViewDefaultEvent(_component);
                    }
                    else
                    {
                        designer.DoDefaultAction();
                    }
                }
            }
        }

        /// <summary>
        ///  Terminates our drag operation.
        /// </summary>
        private void OnEndDrag(bool cancel)
        {
            _mouseDragLast = InvalidPoint;
            if (!_mouseDragMoved)
            {
                if (_ctrlSelect)
                {
                    ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                    sel?.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary);

                    _ctrlSelect = false;
                }

                return;
            }

            _mouseDragMoved = false;
            _ctrlSelect = false;
            Capture = false;
            OnSetCursor();

            // And now finish the drag.
            Debug.Assert(_tray._selectionUISvc is not null, "We shouldn't be able to begin a drag without this");
            if (_tray._selectionUISvc is not null && _tray._selectionUISvc.Dragging)
            {
                _tray._selectionUISvc.EndDrag(cancel);
            }
        }

        /// <summary>
        ///  Called when the mouse button is pressed down.  Here, we provide drag support for the component.
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs me)
        {
            base.OnMouseDown(me);
            if (_tray.TabOrderActive)
            {
                return;
            }

            _tray.FocusDesigner();

            // If this is the left mouse button, then begin a drag.
            if (me.Button == MouseButtons.Left)
            {
                Capture = true;
                _mouseDragLast = PointToScreen(new Point(me.X, me.Y));

                // If the CTRL key isn't down, select this component, otherwise, we wait until the mouse up.
                // Make sure the component is selected.
                _ctrlSelect = PInvoke.GetKeyState((int)Keys.ControlKey) != 0;
                if (!_ctrlSelect)
                {
                    ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                    // Make sure the component is selected
                    sel?.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary);
                }
            }
        }

        /// <summary>
        ///  Called when the mouse is moved over the component.  We update our drag information here if we're dragging the component around.
        /// </summary>
        protected override void OnMouseMove(MouseEventArgs me)
        {
            base.OnMouseMove(me);
            if (_mouseDragLast == InvalidPoint)
            {
                return;
            }

            if (!_mouseDragMoved)
            {
                Size minDrag = SystemInformation.DragSize;
                Size minDblClick = SystemInformation.DoubleClickSize;
                minDrag.Width = Math.Max(minDrag.Width, minDblClick.Width);
                minDrag.Height = Math.Max(minDrag.Height, minDblClick.Height);
                // we have to make sure the mouse moved farther than the minimum drag distance before we actually start the drag
                Point newPt = PointToScreen(new Point(me.X, me.Y));
                if (_mouseDragLast == InvalidPoint ||
                    (Math.Abs(_mouseDragLast.X - newPt.X) < minDrag.Width &&
                     Math.Abs(_mouseDragLast.Y - newPt.Y) < minDrag.Height))
                {
                    return;
                }
                else
                {
                    _mouseDragMoved = true;
                    // we're on the move, so we're not in a ctrlSelect
                    _ctrlSelect = false;
                }
            }

            try
            {
                // Make sure the component is selected
                ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                sel?.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary);

                // Notify the selection service that all the components are in the "mouse down" mode.
                if (_tray._selectionUISvc is not null && _tray._selectionUISvc.BeginDrag(SelectionRules.Visible | SelectionRules.Moveable, _mouseDragLast.X, _mouseDragLast.Y))
                {
                    OnSetCursor();
                }
            }
            finally
            {
                _mouseDragMoved = false;
                _mouseDragLast = InvalidPoint;
            }
        }

        /// <summary>
        ///  Called when the mouse button is released.  Here, we finish our drag if one was started.
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs me)
        {
            base.OnMouseUp(me);
            OnEndDrag(false);
        }

        /// <summary>
        ///  Called when we are to display our context menu for this component.
        /// </summary>
        private void OnContextMenu(Point location)
        {
            if (!_tray.TabOrderActive)
            {
                Capture = false;
                // Ensure that this component is selected.
                ISelectionService s = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                if (s is not null && !s.GetComponentSelected(_component))
                {
                    s.SetSelectedComponents(new object[] { _component }, SelectionTypes.Replace);
                }

                IMenuCommandService mcs = _tray.MenuService;
                if (mcs is not null)
                {
                    Capture = false;
                    Cursor.Clip = Rectangle.Empty;
                    mcs.ShowContextMenu(MenuCommands.TraySelectionMenu, location.X, location.Y);
                }
            }
        }

        /// <summary>
        ///  Painting for our control.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (_fRecompute)
            {
                _fRecompute = false;
                UpdateIconInfo();
            }

            base.OnPaint(e);
            Rectangle rc = ClientRectangle;
            rc.X += WhiteSpace + _borderWidth;
            rc.Y += _borderWidth;
            rc.Width -= (2 * _borderWidth + WhiteSpace);
            rc.Height -= 2 * _borderWidth;
            StringFormat format = new();
            Brush foreBrush = new SolidBrush(ForeColor);
            try
            {
                format.Alignment = StringAlignment.Center;
                if (_tray.ShowLargeIcons)
                {
                    if (_toolboxBitmap is not null)
                    {
                        int x = rc.X + (rc.Width - _cxIcon) / 2;
                        int y = rc.Y + WhiteSpace;
                        e.Graphics.DrawImage(_toolboxBitmap, new Rectangle(x, y, _cxIcon, _cyIcon));
                    }

                    rc.Y += (_cyIcon + WhiteSpace);
                    rc.Height -= _cyIcon;
                    e.Graphics.DrawString(Text, Font, foreBrush, rc, format);
                }
                else
                {
                    if (_toolboxBitmap is not null)
                    {
                        int y = rc.Y + (rc.Height - _cyIcon) / 2;
                        e.Graphics.DrawImage(_toolboxBitmap, new Rectangle(rc.X, y, _cxIcon, _cyIcon));
                    }

                    rc.X += (_cxIcon + _borderWidth);
                    rc.Width -= _cxIcon;
                    rc.Y += 3;
                    e.Graphics.DrawString(Text, Font, foreBrush, rc);
                }
            }

            finally
            {
                format?.Dispose();

                foreBrush?.Dispose();
            }

            // If this component is being inherited, paint it as such
            if (!InheritanceAttribute.NotInherited.Equals(_inheritanceAttribute))
            {
                InheritanceUI iui = _tray.InheritanceUI;
                if (iui is not null)
                {
                    e.Graphics.DrawImage(InheritanceUI.InheritanceGlyph, 0, 0);
                }
            }
        }

        /// <summary>
        ///  Overrides control's FontChanged.  Here we re-adjust our size if the font changes.
        /// </summary>
        protected override void OnFontChanged(EventArgs e)
        {
            AdjustSize();
            base.OnFontChanged(e);
        }

        /// <summary>
        ///  Overrides control's LocationChanged.  Here, we make sure that any glyphs associated with us are also relocated.
        /// </summary>
        protected override void OnLocationChanged(EventArgs e)
        {
            _tray._glyphManager?.UpdateLocation(this);
        }

        /// <summary>
        ///  Overrides control's TextChanged.  Here we re-adjust our size if the font changes.
        /// </summary>
        protected override void OnTextChanged(EventArgs e)
        {
            AdjustSize();
            base.OnTextChanged(e);
        }

        /// <summary>
        ///  Called each time the cursor needs to be set.  The ControlDesigner behavior here will set the cursor to one of three things:
        ///  1.  If the selection UI service shows a locked selection, or if there is no location property on the control, then the default arrow will be set.
        ///  2.  Otherwise, the four headed arrow will be set to indicate that the component can be clicked and moved.
        ///  3.  If the user is currently dragging a component, the crosshair cursor will be used instead of the four headed arrow.
        /// </summary>
        private void OnSetCursor()
        {
            // Check that the component is not locked.
            PropertyDescriptor prop;
            try
            {
                prop = TypeDescriptor.GetProperties(_component)["Locked"];
            }
            catch (FileNotFoundException e)
            {
                // In case an unhandled exception was encountered, we don't want to leave the cursor with some strange shape Currently we have watson logs with FileNotFoundException only, so we are scoping the catch only to that type.
                Cursor.Current = Cursors.Default;
                Debug.Fail(e.Message);
                return;
            }

            if (prop is not null && ((bool)prop.GetValue(_component)))
            {
                Cursor.Current = Cursors.Default;
                return;
            }

            // Ask the tray to see if the tab order UI is not running.
            if (_tray.TabOrderActive)
            {
                Cursor.Current = Cursors.Default;
                return;
            }

            if (_mouseDragMoved)
            {
                Cursor.Current = Cursors.Default;
            }
            else if (_mouseDragLast != InvalidPoint)
            {
                Cursor.Current = Cursors.Cross;
            }
            else
            {
                Cursor.Current = Cursors.SizeAll;
            }
        }

        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            if (!_tray.AutoArrange ||
                (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
                (specified & BoundsSpecified.Height) == BoundsSpecified.Height)
            {
                base.SetBoundsCore(x, y, width, height, specified);
            }

            Rectangle bounds = Bounds;
            Size parentGridSize = _tray.ParentGridSize;
            if (Math.Abs(bounds.X - x) > parentGridSize.Width || Math.Abs(bounds.Y - y) > parentGridSize.Height)
            {
                base.SetBoundsCore(x, y, width, height, specified);
            }
        }

        protected override void SetVisibleCore(bool value)
        {
            if (value && !_tray.CanDisplayComponent(_component))
            {
                return;
            }

            base.SetVisibleCore(value);
        }

        public override string ToString() => $"ComponentTray: {_component}";

        internal void UpdateIconInfo()
        {
            ToolboxBitmapAttribute attr = (ToolboxBitmapAttribute)TypeDescriptor.GetAttributes(_component)[typeof(ToolboxBitmapAttribute)];
            if (attr is not null)
            {
                _toolboxBitmap = attr.GetImage(_component, _tray.ShowLargeIcons);
            }

            // Get the size of the bitmap so we can size our component correctly.
            if (_toolboxBitmap is null)
            {
                _cxIcon = 0;
                _cyIcon = SystemInformation.IconSize.Height;
            }
            else
            {
                Size sz = _toolboxBitmap.Size;
                _cxIcon = sz.Width;
                _cyIcon = sz.Height;
            }

            AdjustSize();
        }

        /// <summary>
        ///  This creates a method signature in the source code file for the default event on the component and navigates the user's cursor to that location.
        /// </summary>
        public virtual void ViewDefaultEvent(IComponent component)
        {
            EventDescriptor defaultEvent = TypeDescriptor.GetDefaultEvent(component);
            PropertyDescriptor defaultPropEvent = null;
            bool eventChanged = false;
            IEventBindingService eps = (IEventBindingService)GetService(typeof(IEventBindingService));
            if (eps is not null)
            {
                defaultPropEvent = eps.GetEventProperty(defaultEvent);
            }

            // If we couldn't find a property for this event, or if the property is read only, then abort and just show the code.
            if (defaultPropEvent is null || defaultPropEvent.IsReadOnly)
            {
                eps?.ShowCode();
                return;
            }

            string handler = (string)defaultPropEvent.GetValue(component);

            // If there is no handler set, set one now.
            if (handler is null)
            {
                eventChanged = true;
                handler = eps.CreateUniqueMethodName(component, defaultEvent);
            }

            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            DesignerTransaction trans = null;

            try
            {
                if (host is not null)
                {
                    trans = host.CreateTransaction(string.Format(SR.WindowsFormsAddEvent, defaultEvent.Name));
                }

                // Save the new value... BEFORE navigating to it!
                if (eventChanged && defaultPropEvent is not null)
                {
                    defaultPropEvent.SetValue(component, handler);
                }

                eps.ShowCode(component, defaultEvent);
            }
            finally
            {
                trans?.Commit();
            }
        }

        /// <summary>
        ///  This method should be called by the extending designer for each message the control would normally receive.  This allows the designer to pre-process messages before allowing them to be routed to the control.
        /// </summary>
        protected override void WndProc(ref Message m)
        {
            switch (m.MsgInternal)
            {
                case PInvoke.WM_SETCURSOR:
                    // We always handle setting the cursor ourselves.
                    OnSetCursor();
                    break;
                case PInvoke.WM_CONTEXTMENU:
                    // We must handle this ourselves.  Control only allows regular Windows Forms context menus, which
                    // doesn't do us much good.  Also, control's button up processing calls DefwndProc first, which
                    // causes a right mouse up to be routed as a WM_CONTEXTMENU.  If we don't respond to it here,
                    // this message will be bubbled up to our parent, which would pop up a container context menu
                    // instead of our own.

                    Point location = PARAM.ToPoint(m.LParamInternal);
                    if (location.X == -1 && location.Y == -1)
                    {
                        // for shift-F10
                        location = MousePosition;
                    }

                    OnContextMenu(location);
                    break;
                case PInvoke.WM_NCHITTEST:
                    if (_tray._glyphManager is not null)
                    {
                        // Make sure that we send our glyphs hit test messages over the TrayControls too.
                        Point pt = PARAM.ToPoint(m.LParamInternal);
                        Point pt1 = PointToClient(default);
                        pt.Offset(pt1.X, pt1.Y);

                        // Offset the location of the traycontrol so we're in component tray coordinates.
                        pt.Offset(Location.X, Location.Y);
                        _tray._glyphManager.GetHitTest(pt);
                    }

                    base.WndProc(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        private class TrayControlAccessibleObject : ControlAccessibleObject
        {
            private readonly ComponentTray _tray;
            public TrayControlAccessibleObject(TrayControl owner, ComponentTray tray) : base(owner)
            {
                _tray = tray;
            }

            private IComponent Component
            {
                get => ((TrayControl)Owner).Component;
            }

            public override AccessibleStates State
            {
                get
                {
                    AccessibleStates state = base.State;
                    ISelectionService s = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                    if (s is not null)
                    {
                        if (s.GetComponentSelected(Component))
                        {
                            state |= AccessibleStates.Selected;
                        }

                        if (s.PrimarySelection == Component)
                        {
                            state |= AccessibleStates.Focused;
                        }
                    }

                    return state;
                }
            }
        }
    }

    private class ComponentTrayGlyphManager
    {
        private Adorner _traySelectionAdorner; // we'll use a single adorner to manage the glyphs
        private Glyph _hitTestedGlyph; // the last glyph we hit tested (can be null)
        private readonly BehaviorService _behaviorSvc;

        /// <summary>
        ///  Constructor that simply creates an empty adorner.
        /// </summary>
        public ComponentTrayGlyphManager(BehaviorService behaviorSvc)
        {
            _behaviorSvc = behaviorSvc;
            _traySelectionAdorner = new Adorner();
        }

        /// <summary>
        ///  This is how we publically expose our glyph collection so that other designer services can 'add value'.
        /// </summary>
        public GlyphCollection SelectionGlyphs
        {
            get => _traySelectionAdorner.Glyphs;
        }

        /// <summary>
        ///  Clears the adorner of glyphs.
        /// </summary>
        public void Dispose()
        {
            if (_traySelectionAdorner is not null)
            {
                _traySelectionAdorner.Glyphs.Clear();
                _traySelectionAdorner = null;
            }
        }

        /// <summary>
        ///  Retrieves a list of glyphs associated with the component.
        /// </summary>
        public GlyphCollection GetGlyphsForComponent(IComponent comp)
        {
            GlyphCollection glyphs = [];
            if (_behaviorSvc is not null && comp is not null)
            {
                if (_behaviorSvc.DesignerActionUI is not null)
                {
                    Glyph g = _behaviorSvc.DesignerActionUI.GetDesignerActionGlyph(comp);
                    if (g is not null)
                    {
                        glyphs.Add(g);
                    }
                }
            }

            return glyphs;
        }

        /// <summary>
        ///  Called from the tray's NCHITTEST message in the WndProc. We use this to loop through our glyphs and identify which one is successfully hit tested.  From here, we know where to send our messages.
        /// </summary>
        public Cursor GetHitTest(Point p)
        {
            for (int i = 0; i < _traySelectionAdorner.Glyphs.Count; i++)
            {
                Cursor hitTestCursor = _traySelectionAdorner.Glyphs[i].GetHitTest(p);
                if (hitTestCursor is not null)
                {
                    _hitTestedGlyph = _traySelectionAdorner.Glyphs[i];
                    return hitTestCursor;
                }
            }

            _hitTestedGlyph = null;
            return null;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.  Here,  we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseDoubleClick(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseDoubleClick(_hitTestedGlyph, e.Button, new Point(e.X, e.Y));
            }

            return false;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.  Here,  we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseDown(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseDown(_hitTestedGlyph, e.Button, new Point(e.X, e.Y));
            }

            return false;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.  Here,  we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseMove(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseMove(_hitTestedGlyph, e.Button, new Point(e.X, e.Y));
            }

            return false;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.  Here,  we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseUp(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseUp(_hitTestedGlyph, e.Button);
            }

            return false;
        }

        /// <summary>
        ///  Called when the comp tray or any tray control paints. This will simply enumerate through the glyphs in our  Adorner and ask them to paint
        /// </summary>
        public void OnPaintGlyphs(PaintEventArgs pe)
        {
            // Paint any glyphs our tray adorner has
            foreach (Glyph g in _traySelectionAdorner.Glyphs)
            {
                g.Paint(pe);
            }
        }

        /// <summary>
        ///  Called when a tray control's location has changed. We'll loop through our glyphs and invalidate any that are associated with the component.
        /// </summary>
        public void UpdateLocation(TrayControl trayControl)
        {
            foreach (Glyph g in _traySelectionAdorner.Glyphs)
            {
                // only look at glyphs that derive from designerglyph base (actions)
                if (g is DesignerActionGlyph desGlyph && ((DesignerActionBehavior)(desGlyph.Behavior)).RelatedComponent.Equals(trayControl.Component))
                {
                    desGlyph.UpdateAlternativeBounds(trayControl.Bounds);
                }
            }
        }
    }

    internal class AutoArrangeComparer : IComparer<Control>
    {
        int IComparer<Control>.Compare(Control o1, Control o2)
        {
            Debug.Assert(o1 is not null && o2 is not null, "Null objects sent for comparison!!!");
            Point tcLoc1 = o1.Location;
            Point tcLoc2 = o2.Location;
            int height = o1.Height / 2;
            // If they are at the same location, they are equal.
            if (tcLoc1.X == tcLoc2.X && tcLoc1.Y == tcLoc2.Y)
            {
                return 0;
            }

            // Is the first control lower than the 2nd...
            if (tcLoc1.Y + height <= tcLoc2.Y)
            {
                return -1;
            }

            // Is the 2nd control lower than the first...
            if (tcLoc2.Y + height <= tcLoc1.Y)
            {
                return 1;
            }

            // Which control is left of the other...
            return ((tcLoc1.X <= tcLoc2.X) ? -1 : 1);
        }
    }

    private class TraySelectionUIHandler : SelectionUIHandler
    {
        private readonly ComponentTray _tray;
        private Size _snapSize = Size.Empty;

        /// <summary>
        ///  Creates a new selection UI handler for the given component tray.
        /// </summary>
        public TraySelectionUIHandler(ComponentTray tray)
        {
            _tray = tray;
            _snapSize = default;
        }

        /// <summary>
        ///  Called when the user has started the drag.
        /// </summary>
        public override bool BeginDrag(object[] components, SelectionRules rules, int initialX, int initialY)
        {
            bool value = base.BeginDrag(components, rules, initialX, initialY);
            _tray.SuspendLayout();
            return value;
        }

        /// <summary>
        ///  Called when the user has completed the drag.  The designer should remove any UI feedback it may be providing.
        /// </summary>
        public override void EndDrag(object[] components, bool cancel)
        {
            base.EndDrag(components, cancel);
            _tray.ResumeLayout();
        }

        /// <summary>
        ///  Retrieves the base component for the selection handler.
        /// </summary>
        protected override IComponent GetComponent()
        {
            return _tray;
        }

        /// <summary>
        ///  Retrieves the base component's UI control for the selection handler.
        /// </summary>
        protected override Control GetControl()
        {
            return _tray;
        }

        /// <summary>
        ///  Retrieves the UI control for the given component.
        /// </summary>
        protected override Control GetControl(IComponent component)
        {
            return TrayControl.FromComponent(component);
        }

        /// <summary>
        ///  Retrieves the current grid snap size we should snap objects to.
        /// </summary>
        protected override Size GetCurrentSnapSize()
        {
            return _snapSize;
        }

        /// <summary>
        ///  We use this to request often-used services.
        /// </summary>
        protected override object GetService(Type serviceType)
        {
            return _tray.GetService(serviceType);
        }

        /// <summary>
        ///  Determines if the selection UI handler should attempt to snap objects to a grid.
        /// </summary>
        protected override bool GetShouldSnapToGrid()
        {
            return false;
        }

        /// <summary>
        ///  Given a rectangle, this updates the dimensions of it with any grid snaps and returns a new rectangle.  If no changes to the rectangle's size were needed, this may return the same rectangle.
        /// </summary>
        public override Rectangle GetUpdatedRect(Rectangle originalRect, Rectangle dragRect, bool updateSize)
        {
            return dragRect;
        }

        /// <summary>
        ///  Asks the handler to set the appropriate cursor
        /// </summary>
        public override void SetCursor()
        {
            _tray.OnSetCursor();
        }
    }

    private class TrayOleDragDropHandler : OleDragDropHandler
    {
        public TrayOleDragDropHandler(SelectionUIHandler selectionHandler, IServiceProvider serviceProvider, IOleDragClient client) : base(selectionHandler, serviceProvider, client)
        {
        }

        protected override bool CanDropDataObject(IDataObject dataObj)
        {
            ICollection comps = null;
            if (dataObj is not null)
            {
                if (dataObj is ComponentDataObjectWrapper cdow)
                {
                    ComponentDataObject cdo = cdow.InnerData;
                    comps = cdo.Components;
                }
                else
                {
                    try
                    {
                        object serializationData = dataObj.GetData(DataFormat, true);
                        if (serializationData is null)
                        {
                            return false;
                        }

                        IDesignerSerializationService ds = (IDesignerSerializationService)GetService(typeof(IDesignerSerializationService));
                        if (ds is null)
                        {
                            return false;
                        }

                        comps = ds.Deserialize(serializationData);
                    }
                    catch (Exception e) when (!e.IsCriticalException())
                    {
                        // We return false on any exception.
                    }
                }
            }

            if (comps is not null && comps.Count > 0)
            {
                foreach (object comp in comps)
                {
                    if (comp is Point)
                    {
                        continue;
                    }

                    if (comp is Control or not IComponent)
                    {
                        return false;
                    }
                }

                return true;
            }

            return false;
        }
    }
}
