Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Drawing

Public Class Highlighter
    Inherits Component

    Private _Highlights As Dictionary(Of Control, eHighlightColor) = New Dictionary(Of Control, eHighlightColor)()
    Private _HighlightOnFocus As Dictionary(Of Control, Boolean) = New Dictionary(Of Control, Boolean)()

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If _ContainerControl IsNot Nothing Then
            RemoveHandler _ContainerControl.SizeChanged, AddressOf ContainerControlSizeChanged
            RemoveHandler _ContainerControl.HandleCreated, AddressOf ContainerControlHandleCreated
        End If

        If _HighlightPanel IsNot Nothing AndAlso _HighlightPanel.Parent Is Nothing AndAlso Not _HighlightPanel.IsDisposed Then
            _HighlightPanel.Dispose()
            _HighlightPanel = Nothing
        Else
            _HighlightPanel = Nothing
        End If

        MyBase.Dispose(disposing)
    End Sub

    <DefaultValue(False), Localizable(True), Description("Indicates whether control is highlighted when it receives input focus.")>
    Public Function GetHighlightOnFocus(ByVal c As Control) As Boolean
        If _HighlightOnFocus.ContainsKey(c) Then
            Return _HighlightOnFocus(c)
        End If

        Return False
    End Function

    Public Sub SetHighlightOnFocus(ByVal c As Control, ByVal highlight As Boolean)
        If c Is Nothing Then Throw New NullReferenceException()

        If _HighlightOnFocus.ContainsKey(c) Then

            If Not highlight Then
                RemoveHighlightOnFocus(_HighlightOnFocus, c)
            End If

            Return
        End If

        If highlight Then AddHighlightOnFocus(_HighlightOnFocus, c)
    End Sub

    Private Sub AddHighlightOnFocus(ByVal highlightOnFocus As Dictionary(Of Control, Boolean), ByVal c As Control)
        AddHandler c.Enter, AddressOf ControlHighlightEnter
        AddHandler c.Leave, AddressOf ControlHighlightLeave
        AddHandler c.VisibleChanged, AddressOf ControlHighlightVisibleChanged
        highlightOnFocus.Add(c, True)
    End Sub

    Private Sub ControlHighlightVisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
        If _HighlightPanel IsNot Nothing AndAlso _HighlightPanel.FocusHighlightControl = sender Then UpdateHighlighterRegion()
    End Sub

    Private Sub ControlHighlightLeave(ByVal sender As Object, ByVal e As EventArgs)
        If _HighlightPanel IsNot Nothing Then _HighlightPanel.FocusHighlightControl = Nothing
        UpdateHighlighterRegion()
    End Sub

    Private Sub ControlHighlightEnter(ByVal sender As Object, ByVal e As EventArgs)
        If _HighlightPanel IsNot Nothing Then
            If Not _HighlightPanel.Visible Then _HighlightPanel.Visible = True
            _HighlightPanel.BringToFront()
            _HighlightPanel.FocusHighlightControl = CType(sender, Control)
        End If

        UpdateHighlighterRegion()
    End Sub

    Private Sub RemoveHighlightOnFocus(ByVal highlightOnFocus As Dictionary(Of Control, Boolean), ByVal c As Control)
        RemoveHandler c.Enter, AddressOf ControlHighlightEnter
        RemoveHandler c.Leave, AddressOf ControlHighlightLeave
        RemoveHandler c.VisibleChanged, AddressOf ControlHighlightVisibleChanged
        highlightOnFocus.Remove(c)
    End Sub

    <DefaultValue(eHighlightColor.None), Localizable(True), Description("Indicates the highlight color that is applied to the control.")>
    Public Function GetHighlightColor(ByVal c As Control) As eHighlightColor
        If _Highlights.ContainsKey(c) Then
            Return _Highlights(c)
        End If

        Return eHighlightColor.None
    End Function

    Public Sub SetHighlightColor(ByVal c As Control, ByVal highlightColor As eHighlightColor)
        If _Highlights.ContainsKey(c) Then

            If highlightColor = eHighlightColor.None Then
                RemoveHighlight(_Highlights, c)
            Else
                Dim color As eHighlightColor = _Highlights(c)
                RemoveHighlight(_Highlights, c)
                AddHighlight(_Highlights, c, highlightColor)
            End If
        ElseIf highlightColor <> eHighlightColor.None Then
            AddHighlight(_Highlights, c, highlightColor)
        End If
    End Sub

    Private _TabControl2 As Dictionary(Of System.Windows.Forms.TabControl, Integer) = New Dictionary(Of System.Windows.Forms.TabControl, Integer)()
    Private _ParentPanel As Dictionary(Of Panel, Integer) = New Dictionary(Of Panel, Integer)()

    Private Sub AddHighlight(ByVal highlights As Dictionary(Of Control, eHighlightColor), ByVal c As Control, ByVal highlightColor As eHighlightColor)
        highlights.Add(c, highlightColor)
        AddHandler c.LocationChanged, New EventHandler(AddressOf ControlLocationChanged)
        AddHandler c.SizeChanged, New EventHandler(AddressOf ControlSizeChanged)
        AddHandler c.VisibleChanged, New EventHandler(AddressOf ControlVisibleChanged)

        If _HighlightPanel IsNot Nothing Then
            If Not _HighlightPanel.Visible Then _HighlightPanel.Visible = True
            _HighlightPanel.BringToFront()
        End If

        If c.Parent Is Nothing Then
            AddHandler c.ParentChanged, AddressOf ControlParentChanged
        Else
            AddTabControlHandlers(c)
        End If

        UpdateHighlighterRegion()
    End Sub

    Private Sub ControlParentChanged(ByVal sender As Object, ByVal e As EventArgs)
        Dim c As Control = CType(sender, Control)
        RemoveHandler c.ParentChanged, AddressOf ControlParentChanged
        AddTabControlHandlers(c)
    End Sub

    Private Sub AddTabControlHandlers(ByVal c As Control)
        Dim tab2 As System.Windows.Forms.TabControl = TryCast(GetParentControl(c, GetType(System.Windows.Forms.TabControl)), System.Windows.Forms.TabControl)

        If tab2 IsNot Nothing Then

            If _TabControl2.ContainsKey(tab2) Then
                _TabControl2(tab2) = _TabControl2(tab2) + 1
            Else
                _TabControl2.Add(tab2, 1)
                AddHandler tab2.SelectedIndexChanged, AddressOf WinFormsTabSelectedIndexChanged
            End If
        Else
            Dim parentPanel As Panel = TryCast(GetParentControl(c, GetType(Panel)), Panel)

            If parentPanel IsNot Nothing Then

                If _ParentPanel.ContainsKey(parentPanel) Then
                    _ParentPanel(parentPanel) = _ParentPanel(parentPanel) + 1
                Else
                    _ParentPanel.Add(parentPanel, 1)
                    AddHandler parentPanel.Resize, AddressOf ParentPanelResized
                    AddHandler parentPanel.LocationChanged, AddressOf ParentPanelLocationChanged
                End If
            End If
        End If
    End Sub

    Private Sub ParentPanelLocationChanged(ByVal sender As Object, ByVal e As EventArgs)
        UpdateHighlights()
    End Sub

    Private Sub ParentPanelResized(ByVal sender As Object, ByVal e As EventArgs)
        UpdateHighlights()
    End Sub

    Private Sub WinFormsTabSelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
        UpdateHighlighterRegion()
    End Sub

    Private Function GetParentControl(ByVal c As Control, ByVal parentType As Type) As Control
        Dim parent As Control = c.Parent

        While parent IsNot Nothing
            If parentType.IsAssignableFrom(parent.[GetType]()) Then Return parent
            parent = parent.Parent
        End While

        Return Nothing
    End Function

    Private Sub ControlVisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
        UpdateHighlighterRegion()
    End Sub

    Private Sub ControlSizeChanged(ByVal sender As Object, ByVal e As EventArgs)
        UpdateHighlighterRegion()
    End Sub

    Private Sub ControlLocationChanged(ByVal sender As Object, ByVal e As EventArgs)
        UpdateHighlighterRegion()
    End Sub

    Private Sub UpdateHighlighterRegion()
        If _HighlightPanel IsNot Nothing Then _HighlightPanel.UpdateRegion()
    End Sub

    Public Sub UpdateHighlights()
        UpdateHighlighterRegion()
    End Sub

    Private Sub RemoveHighlight(ByVal highlights As Dictionary(Of Control, eHighlightColor), ByVal c As Control)
        highlights.Remove(c)
        RemoveHandler c.LocationChanged, New EventHandler(AddressOf ControlLocationChanged)
        RemoveHandler c.SizeChanged, New EventHandler(AddressOf ControlSizeChanged)
        RemoveHandler c.VisibleChanged, New EventHandler(AddressOf ControlVisibleChanged)
        Dim tab2 As System.Windows.Forms.TabControl = TryCast(GetParentControl(c, GetType(System.Windows.Forms.TabControl)), System.Windows.Forms.TabControl)

        If tab2 IsNot Nothing Then

            If _TabControl2.ContainsKey(tab2) Then

                If _TabControl2(tab2) = 1 Then
                    _TabControl2.Remove(tab2)
                    RemoveHandler tab2.SelectedIndexChanged, AddressOf WinFormsTabSelectedIndexChanged
                Else
                    _TabControl2(tab2) = _TabControl2(tab2) - 1
                End If
            End If
        Else
            Dim parentPanel As Panel = TryCast(GetParentControl(c, GetType(Panel)), Panel)

            If parentPanel IsNot Nothing Then

                If _ParentPanel.ContainsKey(parentPanel) Then

                    If _ParentPanel(parentPanel) = 1 Then
                        _ParentPanel.Remove(parentPanel)
                        RemoveHandler parentPanel.LocationChanged, AddressOf ParentPanelLocationChanged
                        RemoveHandler parentPanel.SizeChanged, AddressOf ParentPanelResized
                    Else
                        _ParentPanel(parentPanel) = _ParentPanel(parentPanel) - 1
                    End If
                End If
            End If
        End If

        UpdateHighlighterRegion()
    End Sub

    Friend ReadOnly Property Highlights As Dictionary(Of Control, eHighlightColor)
        Get
            Return _Highlights
        End Get
    End Property

    Private _FocusHighlightColor As eHighlightColor = eHighlightColor.Blue

    <DefaultValue(eHighlightColor.Blue), Category("Appearance"), Description("Indicates the highlight focus color."), Localizable(True)>
    Public Property FocusHighlightColor As eHighlightColor
        Get
            Return _FocusHighlightColor
        End Get
        Set(ByVal value As eHighlightColor)
            _FocusHighlightColor = value

            If _HighlightPanel IsNot Nothing Then
                _HighlightPanel.FocusHighlightColor = value
                UpdateHighlighterRegion()
            End If
        End Set
    End Property

    Private _HighlightPanel As HighlightPanel = Nothing
    Private _ContainerControl As Control = Nothing

    <Description("Indicates container control highlighter is bound to. Should be set to parent form."), Category("Behavior")>
    Public Property ContainerControl As Control
        Get
            Return _ContainerControl
        End Get
        Set(ByVal value As Control)

            If Me.DesignMode Then
                _ContainerControl = value
                Return
            End If

            If _ContainerControl IsNot value Then

                If _ContainerControl IsNot Nothing Then
                    RemoveHandler _ContainerControl.SizeChanged, AddressOf ContainerControlSizeChanged
                    RemoveHandler _ContainerControl.HandleCreated, AddressOf ContainerControlHandleCreated
                    If _HighlightPanel IsNot Nothing AndAlso _HighlightPanel.Parent Is _ContainerControl Then _ContainerControl.Controls.Remove(_HighlightPanel)
                End If

                _ContainerControl = value

                If _ContainerControl IsNot Nothing Then

                    If _HighlightPanel Is Nothing Then
                        _HighlightPanel = New HighlightPanel(_Highlights)
                        _HighlightPanel.FocusHighlightColor = _FocusHighlightColor
                        _HighlightPanel.Margin = New System.Windows.Forms.Padding(0)
                        _HighlightPanel.Padding = New System.Windows.Forms.Padding(0)
                        _HighlightPanel.CustomHighlightColors = _CustomHighlightColors
                        _HighlightPanel.Visible = False
                    End If

                    AddHandler _ContainerControl.SizeChanged, AddressOf ContainerControlSizeChanged
                    AddHandler _ContainerControl.HandleCreated, AddressOf ContainerControlHandleCreated
                    _ContainerControl.Controls.Add(_HighlightPanel)
                    UpdateHighlightPanelBounds()
                End If
            End If
        End Set
    End Property

    Private Sub ContainerControlHandleCreated(ByVal sender As Object, ByVal e As EventArgs)
        If _Highlights.Count > 0 AndAlso _HighlightPanel IsNot Nothing AndAlso Not _HighlightPanel.Visible Then _HighlightPanel.Visible = True
    End Sub

    Private Sub UpdateHighlightPanelBounds()
        Dim bounds As Rectangle = New Rectangle(0, 0, _ContainerControl.ClientRectangle.Width, _ContainerControl.ClientRectangle.Height)

        If _HighlightPanel IsNot Nothing Then
            If TypeOf _HighlightPanel.Parent Is Form Then
                Dim form As Form = TryCast(_HighlightPanel.Parent, Form)

                If form.AutoSize Then
                    bounds.X += form.Padding.Left
                    bounds.Y += form.Padding.Top
                    bounds.Width -= form.Padding.Horizontal
                    bounds.Height -= form.Padding.Vertical
                End If
            End If

            If _HighlightPanel.Bounds.Equals(bounds) Then
                _HighlightPanel.UpdateRegion()
            Else
                _HighlightPanel.Bounds = bounds
            End If

            _HighlightPanel.BringToFront()
        End If
    End Sub

    Private _DelayTimer As Timer = Nothing

    Private Sub ContainerControlSizeChanged(ByVal sender As Object, ByVal e As EventArgs)
        Dim form As Form = TryCast(sender, Form)

        If form IsNot Nothing Then

            If _DelayTimer Is Nothing Then
                _DelayTimer = New Timer()
                _DelayTimer.Interval = 100
                AddHandler _DelayTimer.Tick, New EventHandler(AddressOf DelayTimerTick)
                _DelayTimer.Start()
            End If

            Return
        End If

        UpdateHighlightPanelBounds()
    End Sub

    Private Sub DelayTimerTick(ByVal sender As Object, ByVal e As EventArgs)
        Dim timer As Timer = _DelayTimer
        _DelayTimer = Nothing
        RemoveHandler timer.Tick, New EventHandler(AddressOf DelayTimerTick)
        timer.[Stop]()
        timer.Dispose()
        UpdateHighlightPanelBounds()
    End Sub

    Private _CustomHighlightColors As Color() = Nothing

    <Category("Appearance"), Description("Array of colors used to render custom highlight color. Control expects 3 colors in array to be specified which define the highlight border.")>
    Public Property CustomHighlightColors As Color()
        Get
            Return _CustomHighlightColors
        End Get
        Set(ByVal value As Color())
            _CustomHighlightColors = value

            If _HighlightPanel IsNot Nothing Then
                _HighlightPanel.CustomHighlightColors = _CustomHighlightColors
                _HighlightPanel.Invalidate()
            End If
        End Set
    End Property

End Class

Public Enum eHighlightColor
    None
    Red
    Blue
    Green
    Orange
    Custom
End Enum
