﻿// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2009, 2012-2016, 2018, 2021 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "LinkControl.h"
#include "CommonAppUtils.h"

const UINT CLinkControl::LK_LINKITEMCLICKED = ::RegisterWindowMessage(L"LK_LINKITEMCLICKED");

CLinkControl::CLinkControl()
    : m_hLinkCursor(nullptr)
    , m_bOverControl(false)
{
}

CLinkControl::~CLinkControl()
{
    /*
    * No need to call DestroyCursor() for cursors acquired through
    * LoadCursor().
    */
    m_normalFont.DeleteObject();
    m_underlineFont.DeleteObject();
}

void CLinkControl::PreSubclassWindow()
{
    CStatic::PreSubclassWindow();

    ModifyStyle(0, SS_NOTIFY);

    m_hLinkCursor = ::LoadCursor(nullptr, IDC_HAND); // Load Windows' hand cursor
    if (!m_hLinkCursor)                              // if not available, use the standard Arrow cursor
    {
        m_hLinkCursor = ::LoadCursor(nullptr, IDC_ARROW);
    }

    // Create an updated font by adding an underline.
    LOGFONT lf;
    CFont*  pFont = GetFont();
    if (pFont)
        pFont->GetObject(sizeof(lf), &lf);
    else
    {
        NONCLIENTMETRICS metrics = {0};
        metrics.cbSize           = sizeof(NONCLIENTMETRICS);
        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
        memcpy_s(&lf, sizeof(LOGFONT), &metrics.lfMessageFont, sizeof(LOGFONT));
    }

    lf.lfWeight = FW_BOLD;
    m_normalFont.CreateFontIndirect(&lf);

    lf.lfUnderline = TRUE;
    m_underlineFont.CreateFontIndirect(&lf);

    SetFont(&m_normalFont, FALSE);

    CCommonAppUtils::SetAccProperty(GetSafeHwnd(), PROPID_ACC_ROLE, ROLE_SYSTEM_LINK);
    UpdateAccState();
}

BEGIN_MESSAGE_MAP(CLinkControl, CStatic)
    ON_WM_SETCURSOR()
    ON_WM_MOUSEMOVE()
    ON_WM_CAPTURECHANGED()
    ON_WM_ERASEBKGND()
    ON_WM_SETFOCUS()
    ON_WM_KILLFOCUS()
    ON_WM_GETDLGCODE()
    ON_WM_KEYDOWN()
    ON_CONTROL_REFLECT(STN_CLICKED, OnClicked)
    ON_WM_ENABLE()
END_MESSAGE_MAP()

void CLinkControl::OnMouseMove(UINT /*nFlags*/, CPoint pt)
{
    if (m_bOverControl)
    {
        RECT rect;
        GetClientRect(&rect);

        if (!PtInRect(&rect, pt))
            ReleaseCapture();
    }
    else
    {
        m_bOverControl = TRUE;
        SetFont(&m_underlineFont, FALSE);
        InvalidateRect(nullptr, FALSE);
        SetCapture();
    }
}

// ReSharper disable once CppMemberFunctionMayBeConst
BOOL CLinkControl::OnSetCursor(CWnd* /*pWnd*/, UINT /*nHitTest*/, UINT /*message*/)
{
    ::SetCursor(m_hLinkCursor);
    return TRUE;
}

void CLinkControl::OnCaptureChanged(CWnd* /*pWnd*/)
{
    m_bOverControl = FALSE;
    SetFont(&m_normalFont, FALSE);
    InvalidateRect(nullptr, FALSE);
}

void CLinkControl::OnSetFocus(CWnd* pOldWnd)
{
    DrawFocusRect();
    __super::OnSetFocus(pOldWnd);
}

void CLinkControl::OnKillFocus(CWnd* pOldWnd)
{
    ClearFocusRect();
    __super::OnKillFocus(pOldWnd);
}

void CLinkControl::DrawFocusRect() const
{
    CWnd* parent = GetParent();

    if (parent)
    {
        // calculate where to draw focus rectangle, in screen coords
        RECT rc;
        GetWindowRect(&rc);

        InflateRect(&rc, 1, 1); // add one pixel all around
        parent->ScreenToClient(&rc);

        CClientDC dcParent(parent);
        dcParent.DrawFocusRect(&rc);
    }
}

void CLinkControl::ClearFocusRect() const
{
    CWnd* parent = GetParent();

    if (parent)
    {
        // calculate where to draw focus rectangle, in screen coords
        RECT rc;
        GetWindowRect(&rc);

        InflateRect(&rc, 1, 1); // add one pixel all around
        parent->ScreenToClient(&rc);
        // Ask parent to redraw area where we rendered focus rect before.
        parent->InvalidateRect(&rc);
    }
}

void CLinkControl::UpdateAccState() const
{
    DWORD state = STATE_SYSTEM_READONLY;
    if (!IsWindowEnabled())
        state |= STATE_SYSTEM_UNAVAILABLE;

    CCommonAppUtils::SetAccProperty(GetSafeHwnd(), PROPID_ACC_STATE, state);
}

void CLinkControl::NotifyParent(UINT msg) const
{
    ::PostMessage(GetParent()->GetSafeHwnd(), msg, reinterpret_cast<WPARAM>(GetSafeHwnd()), static_cast<LPARAM>(0));
}

UINT CLinkControl::OnGetDlgCode()
{
    UINT       dlgCode = CStatic::OnGetDlgCode();
    const MSG* pMsg    = CWnd::GetCurrentMessage();

    // we want all keys to get the return key
    dlgCode |= DLGC_WANTALLKEYS;
    dlgCode |= DLGC_BUTTON;
    // but we don't want the tab key since that should be used in dialogs
    // to switch the focus
    dlgCode &= ~DLGC_WANTTAB;
    dlgCode &= ~DLGC_STATIC;

    if (pMsg->lParam &&
        reinterpret_cast<MSG*>(pMsg->lParam)->message == WM_KEYDOWN &&
        reinterpret_cast<MSG*>(pMsg->lParam)->wParam == VK_TAB)
    {
        dlgCode &= ~DLGC_WANTMESSAGE;
    }

    return dlgCode;
}

// ReSharper disable once CppMemberFunctionMayBeConst
void CLinkControl::OnKeyDown(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/)
{
    if (nChar == VK_SPACE || nChar == VK_RETURN)
    {
        NotifyParent(LK_LINKITEMCLICKED);
    }
}

// ReSharper disable once CppMemberFunctionMayBeConst
void CLinkControl::OnClicked()
{
    NotifyParent(LK_LINKITEMCLICKED);
}

void CLinkControl::OnEnable(BOOL enabled)
{
    CStatic::OnEnable(enabled);

    UpdateAccState();
}

BOOL CLinkControl::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    switch (message)
    {
        case BM_CLICK:
            NotifyParent(LK_LINKITEMCLICKED);
            break;
    }

    return CStatic::OnWndMsg(message, wParam, lParam, pResult);
}
