/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hlinkmgr.cpp,v 1.2.22.1 2004/07/09 01:51:47 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

// system
#include <string.h>
// From include
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxresult.h"
#include "hxcom.h"
#include "ihxpckts.h"
#include "hxcomm.h"
#include "hxwin.h"
// From pncont
#include "hxslist.h"
#include "hxstring.h"
// From pnmisc
#include "baseobj.h"
// From pxrend
#include "hlinkmgr.h"
// From pndebug
#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static char HX_THIS_FILE[] = __FILE__;
#endif

PXHyperlinkManager::PXHyperlinkManager()
{
    m_lRefCount                    = 0;
    m_pCommonClassFactory          = NULL;
    m_ulWidth                      = 0;
    m_ulHeight                     = 0;
    m_pDefaultLinkStr              = NULL;
    m_bHaveDefault                 = FALSE;
    m_pLinkPairList                = NULL;
    m_bSomeLinkHasKeyboardFocus    = FALSE;
    m_bDefaultLinkHasKeyboardFocus = FALSE;
    m_pKeyboardFocusLink           = NULL;
}

PXHyperlinkManager::~PXHyperlinkManager()
{
    HX_RELEASE(m_pCommonClassFactory);
    HX_RELEASE(m_pDefaultLinkStr);
    ClearLinkPairList();
    HX_DELETE(m_pLinkPairList);
}

STDMETHODIMP PXHyperlinkManager::QueryInterface(REFIID riid, void** ppvObj)
{
    HX_RESULT retVal = HXR_OK;

    if (ppvObj)
    {
        // Set default
        *ppvObj = NULL;
        // Check for IID type
        if (IsEqualIID(riid, IID_IUnknown))
        {
            AddRef();
            *ppvObj = (IUnknown*) this;
        }
        else
        {
            retVal = HXR_NOINTERFACE;
        }
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

STDMETHODIMP_(UINT32) PXHyperlinkManager::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

STDMETHODIMP_(UINT32) PXHyperlinkManager::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;

    return 0;
}

HX_RESULT PXHyperlinkManager::Init(IUnknown* pContext, UINT32 ulW, UINT32 ulH)
{
    HX_RESULT retVal = HXR_FAIL;

    if (pContext && ulW && ulH)
    {
        // Assign width and height
        m_ulWidth  = ulW;
        m_ulHeight = ulH;
        // Create the link pair list
        ClearLinkPairList();
        HX_DELETE(m_pLinkPairList);
        m_pLinkPairList = new CHXSimpleList();
        if (m_pLinkPairList)
        {
            // Get the IHXCommonClassFactory interface
            HX_RELEASE(m_pCommonClassFactory);
            retVal = pContext->QueryInterface(IID_IHXCommonClassFactory,
                                              (void**) &m_pCommonClassFactory);
        }
    }

    return retVal;
}

HX_RESULT PXHyperlinkManager::SetDefaultLink(const char *pszDefaultLink)
{
    HX_RESULT retVal = HXR_OK;

    if (pszDefaultLink                        &&
        strlen(pszDefaultLink)            > 0 &&
        strspn(pszDefaultLink, " \r\n\t") < strlen(pszDefaultLink))
    {
        if (m_pCommonClassFactory)
        {
            // Allocate an IHXBuffer
            HX_RELEASE(m_pDefaultLinkStr);
            retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**) &m_pDefaultLinkStr);
            if (SUCCEEDED(retVal))
            {
                // Set the value of the buffer
                retVal = m_pDefaultLinkStr->Set((const BYTE*) pszDefaultLink, strlen(pszDefaultLink) + 1);
                if (SUCCEEDED(retVal))
                {
                    m_bHaveDefault = TRUE;
                }
            }
            else
            {
                retVal = HXR_OUTOFMEMORY;
            }
        }
        else
        {
            retVal = HXR_UNEXPECTED;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXHyperlinkManager::AddLink(HXxRect cRect, const char* pszLink,
                                      UINT32 ulTabIndex, UINT32 ulLexicalOrder)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pLinkPairList)
    {
        if (cRect.right           <= (INT32) m_ulWidth  &&
            cRect.bottom          <= (INT32) m_ulHeight &&
            HXxRECT_WIDTH(cRect)  >  0                  &&
            HXxRECT_HEIGHT(cRect) >  0)
        {
            // Check to see if this rect completely obscures a rect which
            // is already in the list. If so, then remove the smaller, older
            // rect from the list. Also we want to check whether the
            // current list includes a link with the same tabindex
            // and lexical order.
            BOOL   bDupTabLex = FALSE;
            UINT32 ulMaxLex   = 0;
            LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
            while (pos)
            {
                PXHyperlink* pListLink =
                    (PXHyperlink*) m_pLinkPairList->GetAt(pos);
                if (pListLink)
                {
                    if (pListLink->m_Rect.left   >= cRect.left  &&
                        pListLink->m_Rect.top    >= cRect.top   &&
                        pListLink->m_Rect.right  <= cRect.right &&
                        pListLink->m_Rect.bottom <= cRect.bottom)
                    {
                        HX_DELETE(pListLink);
                        pos = m_pLinkPairList->RemoveAt(pos);
                    }
                    else
                    {
                        m_pLinkPairList->GetNext(pos);
                        // Find the max lexical order for this tab index
                        if (ulTabIndex == pListLink->m_ulTabIndex)
                        {
                            if (pListLink->m_ulLexicalOrder > ulMaxLex)
                            {
                                ulMaxLex = pListLink->m_ulLexicalOrder;
                            }
                        }
                        // Check for duplicate tabindex and
                        // lexical order members
                        if (ulTabIndex     == pListLink->m_ulTabIndex &&
                            ulLexicalOrder == pListLink->m_ulLexicalOrder)
                        {
                            bDupTabLex = TRUE;
                        }
                    }
                }
                else
                {
                    m_pLinkPairList->GetNext(pos);
                }
            }
            // Create a new link
            PXHyperlink* pLink = new PXHyperlink();
            if (pLink)
            {
                // Clear the return value
                retVal = HXR_OK;
                // Set the rect
                pLink->m_Rect = cRect;
                // Set the tabindex
                pLink->m_ulTabIndex = ulTabIndex;
                // Set the lexical order. If there is another link
                // in the list with the same tabindex and
                // lexical order, then we detected that above. We
                // also computed the max lexical order for that tabindex.
                // So set the lexical order to one bigger than that.
                if (bDupTabLex)
                {
                    ulLexicalOrder = ulMaxLex + 1;
                }
                pLink->m_ulLexicalOrder = ulLexicalOrder;
                // If there is a link, we need to create an IHXBuffer
                if (pszLink)
                {
                    IHXBuffer* pBuffer = NULL;
                    retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer,
                                                                   (void**) &pBuffer);
                    if (SUCCEEDED(retVal))
                    {
                        // Set the value
                        retVal = pBuffer->Set((const BYTE*) pszLink, strlen(pszLink) + 1);
                        if (SUCCEEDED(retVal))
                        {
                            // Set the string buffer
                            pLink->m_pLinkStr = pBuffer;
                            pLink->m_pLinkStr->AddRef();
                        }
                    }
                    HX_RELEASE(pBuffer);
                }
                if (SUCCEEDED(retVal))
                {
                    // Add the link to the list
                    m_pLinkPairList->AddHead((void*) pLink);
                }
            }
            if (FAILED(retVal))
            {
                HX_DELETE(pLink);
            }
        }
    }

    return retVal;
}

BOOL PXHyperlinkManager::GetActiveLink(INT16 sX, INT16 sY, INT32 lCurW, INT32 lCurH,
                                       REF(IHXBuffer*) rpLinkStr)
{
    BOOL bRet = FALSE;

    if (m_pLinkPairList)
    {
        // Sanity check to make sure x,y is inside our overall window
        if (sX >= 0 && sX < lCurW && sY >= 0 && sY < lCurH)
        {
            // Check if scaling is necessary
            BOOL bNeedScaling = FALSE;
            if (lCurW != (INT32) m_ulWidth || lCurH != (INT32) m_ulHeight)
            {
                bNeedScaling = TRUE;
            }

            // Search the list for the first link which holds this point
            LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
            while (pos)
            {
                PXHyperlink* pLink =
                    (PXHyperlink*) m_pLinkPairList->GetNext(pos);
                if (pLink)
                {
                    INT32 lRectX = (INT32) pLink->m_Rect.left;
                    INT32 lRectY = (INT32) pLink->m_Rect.top;
                    INT32 lRectW = (INT32) HXxRECT_WIDTH(pLink->m_Rect);
                    INT32 lRectH = (INT32) HXxRECT_HEIGHT(pLink->m_Rect);
                    if (bNeedScaling)
                    {
                        lRectX = lRectX * lCurW / (INT32) m_ulWidth;
                        lRectY = lRectY * lCurH / (INT32) m_ulHeight;
                        lRectW = lRectW * lCurW / (INT32) m_ulWidth;
                        lRectH = lRectH * lCurH / (INT32) m_ulHeight;
                    }
                    if (sX >= lRectX          &&
                        sY >= lRectY          &&
                        sX <  lRectX + lRectW &&
                        sY <  lRectY + lRectH)
                    {
                        // We found the active rect. If it has a link associated
                        // with it, then it's active. If not, we don't have
                        // an active link. Either way, we need to break.
                        if (pLink->m_pLinkStr)
                        {
                            // Set the out param
                            HX_RELEASE(rpLinkStr);
                            rpLinkStr = pLink->m_pLinkStr;
                            rpLinkStr->AddRef();
                            // Set the return flag
                            bRet = TRUE;
                        }
                        break;
                    }
                }
            }

            // Do we need to assign a default?
            if (!bRet && m_bHaveDefault)
            {
                // We didn't find a link and we DO have a default,
                // so assign the default as the link
                //
                // Set the out param
                HX_RELEASE(rpLinkStr);
                rpLinkStr = m_pDefaultLinkStr;
                rpLinkStr->AddRef();
                // Set the return flag
                bRet = TRUE;
            }
        }
    }

    return bRet;
}

void PXHyperlinkManager::NavigateKeyboardFocus(HXFocusContext eFocus)
{
    switch (eFocus)
    {
        case HXFirstFocus:
            {
                GetFirstLinkWithDefault();
            }
            break;
        case HXUpFocus:
        case HXLeftFocus:
        case HXPrevFocus:
            {
                GetPrevLinkWithDefault();
            }
            break;
        case HXDownFocus:
        case HXRightFocus:
        case HXNextFocus:
            {
                GetNextLinkWithDefault();
            }
            break;
        case HXLastFocus:
            {
                GetLastLinkWithDefault();
            }
            break;
    }
}

BOOL PXHyperlinkManager::GetLinkWithKeyboardFocus(REF(HXxRect)     rRect,
                                                  REF(IHXBuffer*) rpBuffer)
{
    BOOL bRet = FALSE;

    if (m_bSomeLinkHasKeyboardFocus)
    {
        if (m_bDefaultLinkHasKeyboardFocus)
        {
            // Sanity check to make sure we actually
            // HAVE a default link
            if (m_bHaveDefault)
            {
                // The rect is the full window
                rRect.left   = 0;
                rRect.top    = 0;
                rRect.right  = (INT32) m_ulWidth;
                rRect.bottom = (INT32) m_ulHeight;
                // Set the link string
                HX_RELEASE(rpBuffer);
                rpBuffer = m_pDefaultLinkStr;
                rpBuffer->AddRef();
                // Set the return value
                bRet = TRUE;
            }
        }
        else
        {
            // Sanity check to make sure we have set
            // the keyboard focus link pointer
            if (m_pKeyboardFocusLink)
            {
                // Set the rect
                rRect = m_pKeyboardFocusLink->m_Rect;
                // Set the string buffer
                HX_RELEASE(rpBuffer);
                rpBuffer = m_pKeyboardFocusLink->m_pLinkStr;
                rpBuffer->AddRef();
                // Set the return value
                bRet = TRUE;
            }
        }
    }

    return bRet;
}

void PXHyperlinkManager::ClearKeyboardFocus()
{
    m_bSomeLinkHasKeyboardFocus    = FALSE;
    m_bDefaultLinkHasKeyboardFocus = FALSE;
    m_pKeyboardFocusLink           = NULL;
}

void PXHyperlinkManager::ClearLinkPairList()
{
    if (m_pLinkPairList)
    {
        LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
        while (pos)
        {
            PXHyperlink* pLink = (PXHyperlink*) m_pLinkPairList->GetNext(pos);
            HX_DELETE(pLink);
        }
        m_pLinkPairList->RemoveAll();
    }
}

BOOL PXHyperlinkManager::GetFirstLink(REF(PXHyperlink*) rpLink)
{
    return GetNextLink(NULL, rpLink);
}

BOOL PXHyperlinkManager::GetLastLink(REF(PXHyperlink*) rpLink)
{
    return GetPrevLink(NULL, rpLink);
}

BOOL PXHyperlinkManager::GetNextLink(PXHyperlink*      pCurLink,
                                     REF(PXHyperlink*) rpNextLink)
{
    BOOL bRet = FALSE;

    // Do we have a current link?
    if (pCurLink)
    {
        // Run through the list to find the "next" link - 
        // one with the same tab index and higher lexical order
        // or if there are none of those, the one with the 
        // higher tabindex
        if (m_pLinkPairList)
        {
            PXHyperlink* pNextLink = NULL;
            LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
            while (pos)
            {
                PXHyperlink* pLink = (PXHyperlink*) m_pLinkPairList->GetNext(pos);
                if (pLink &&
                    (pLink->m_ulTabIndex     != pCurLink->m_ulTabIndex ||
                     pLink->m_ulLexicalOrder != pCurLink->m_ulLexicalOrder))
                {
                    // Is this link a candidate?
                    if (pLink->m_ulTabIndex > pCurLink->m_ulTabIndex ||
                        (pLink->m_ulTabIndex     == pCurLink->m_ulTabIndex &&
                         pLink->m_ulLexicalOrder >  pCurLink->m_ulLexicalOrder))
                    {
                        // Yes, this might be one. Is it sooner than
                        // the current "next" link?
                        if (!pNextLink ||
                            (pLink->m_ulTabIndex < pNextLink->m_ulTabIndex ||
                             (pLink->m_ulTabIndex == pNextLink->m_ulTabIndex &&
                              pLink->m_ulLexicalOrder < pNextLink->m_ulLexicalOrder)))
                        {
                            pNextLink = pLink;
                        }
                    }
                }
            }
            // Did we get one?
            if (pNextLink)
            {
                // Assign the out parameter
                rpNextLink = pNextLink;
                // Set the return value
                bRet = TRUE;
            }
        }
    }
    else
    {
        // No, no current link, so take the
        // first link (i.e - min tab index, and min
        // lexical order within that tab index)
        if (m_pLinkPairList)
        {
            UINT32       ulMinTab = 0xFFFFFFFF;
            UINT32       ulMinLex = 0xFFFFFFFF;
            PXHyperlink* pMinLink = NULL;
            LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
            while (pos)
            {
                PXHyperlink* pLink = (PXHyperlink*) m_pLinkPairList->GetNext(pos);
                if (pLink &&
                    (pLink->m_ulTabIndex < ulMinTab ||
                     (pLink->m_ulTabIndex     == ulMinTab &&
                      pLink->m_ulLexicalOrder <  ulMinLex)))
                {
                    ulMinTab = pLink->m_ulTabIndex;
                    ulMinLex = pLink->m_ulLexicalOrder;
                    pMinLink = pLink;
                }
            }
            // Did we get one?
            if (pMinLink)
            {
                // Assign the out parameter
                rpNextLink = pMinLink;
                // Set the return value
                bRet = TRUE;
            }
        }
    }

    return bRet;
}

BOOL PXHyperlinkManager::GetPrevLink(PXHyperlink*      pCurLink,
                                     REF(PXHyperlink*) rpPrevLink)
{
    BOOL bRet = FALSE;

    // Do we have a current link?
    if (pCurLink)
    {
        // Run through the list to find the "prev" link - 
        // one with the same tab index and smaller lexical order
        // or if there are none of those, the one with the 
        // smaller tabindex
        if (m_pLinkPairList)
        {
            PXHyperlink* pPrevLink = NULL;
            LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
            while (pos)
            {
                PXHyperlink* pLink = (PXHyperlink*) m_pLinkPairList->GetNext(pos);
                if (pLink &&
                    (pLink->m_ulTabIndex     != pCurLink->m_ulTabIndex ||
                     pLink->m_ulLexicalOrder != pCurLink->m_ulLexicalOrder))
                {
                    // Is this link a candidate?
                    if (pLink->m_ulTabIndex < pCurLink->m_ulTabIndex ||
                        (pLink->m_ulTabIndex     == pCurLink->m_ulTabIndex &&
                         pLink->m_ulLexicalOrder <  pCurLink->m_ulLexicalOrder))
                    {
                        // Yes, this might be one. Is it later than
                        // the current "prev" link?
                        if (!pPrevLink ||
                            (pLink->m_ulTabIndex > pPrevLink->m_ulTabIndex ||
                             (pLink->m_ulTabIndex     == pPrevLink->m_ulTabIndex &&
                              pLink->m_ulLexicalOrder >  pPrevLink->m_ulLexicalOrder)))
                        {
                            pPrevLink = pLink;
                        }
                    }
                }
            }
            // Did we get one?
            if (pPrevLink)
            {
                // Assign the out parameter
                rpPrevLink = pPrevLink;
                // Set the return value
                bRet = TRUE;
            }
        }
    }
    else
    {
        // No, no current link, so take the
        // last link (i.e - max tab index, and max
        // lexical order within that tab index)
        if (m_pLinkPairList)
        {
            INT32        lMaxTab  = -1;
            INT32        lMaxLex  = -1;
            PXHyperlink* pMaxLink = NULL;
            LISTPOSITION pos = m_pLinkPairList->GetHeadPosition();
            while (pos)
            {
                PXHyperlink* pLink = (PXHyperlink*) m_pLinkPairList->GetNext(pos);
                if (pLink)
                {
                    INT32 lTab = (INT32) pLink->m_ulTabIndex;
                    INT32 lLex = (INT32) pLink->m_ulLexicalOrder;
                    if (lTab > lMaxTab ||
                        (lTab == lMaxTab && lLex > lMaxLex))
                    {
                        lMaxTab  = (INT32) pLink->m_ulTabIndex;
                        lMaxLex  = (INT32) pLink->m_ulLexicalOrder;
                        pMaxLink = pLink;
                    }
                }
            }
            // Did we get one?
            if (pMaxLink)
            {
                // Assign the out parameter
                rpPrevLink = pMaxLink;
                // Set the return value
                bRet = TRUE;
            }
        }
    }

    return bRet;
}

void PXHyperlinkManager::GetFirstLinkWithDefault()
{
    // First is always default if present,
    // and first link if no default
    PXHyperlink* pLink = NULL;
    if (m_bHaveDefault)
    {
        m_bSomeLinkHasKeyboardFocus    = TRUE;
        m_bDefaultLinkHasKeyboardFocus = TRUE;
        m_pKeyboardFocusLink           = NULL;
    }
    else if (GetFirstLink(pLink))
    {
        m_bSomeLinkHasKeyboardFocus    = TRUE;
        m_bDefaultLinkHasKeyboardFocus = FALSE;
        m_pKeyboardFocusLink           = pLink;
    }
    else
    {
        ClearKeyboardFocus();
    }
}

void PXHyperlinkManager::GetLastLinkWithDefault()
{
    // Last is GetLastLink() if it returns TRUE
    // and default link if GetLastLink is FALSE
    PXHyperlink* pLink = NULL;
    if (GetLastLink(pLink))
    {
        m_bSomeLinkHasKeyboardFocus    = TRUE;
        m_bDefaultLinkHasKeyboardFocus = FALSE;
        m_pKeyboardFocusLink           = pLink;
    }
    else if (m_bHaveDefault)
    {
        m_bSomeLinkHasKeyboardFocus    = TRUE;
        m_bDefaultLinkHasKeyboardFocus = TRUE;
        m_pKeyboardFocusLink           = NULL;
    }
    else
    {
        ClearKeyboardFocus();
    }
}

void PXHyperlinkManager::GetNextLinkWithDefault()
{
    // Do we already have a focus link?
    if (m_bSomeLinkHasKeyboardFocus)
    {
        // We already have a focus link
        //
        // Is it the default link?
        if (m_bDefaultLinkHasKeyboardFocus)
        {
            // Get the first link
            PXHyperlink* pLink = NULL;
            if (GetFirstLink(pLink))
            {
                m_bSomeLinkHasKeyboardFocus    = TRUE;
                m_bDefaultLinkHasKeyboardFocus = FALSE;
                m_pKeyboardFocusLink           = pLink;
            }
            else
            {
                ClearKeyboardFocus();
            }
        }
        else
        {
            // Get the next link
            PXHyperlink* pLink = NULL;
            if (GetNextLink(m_pKeyboardFocusLink, pLink))
            {
                m_bSomeLinkHasKeyboardFocus    = TRUE;
                m_bDefaultLinkHasKeyboardFocus = FALSE;
                m_pKeyboardFocusLink           = pLink;
            }
            else
            {
                ClearKeyboardFocus();
            }
        }
    }
    else
    {
        // We don't currently have a focus link, so this is
        // the same as GetFirstLinkWithDefault()
        GetFirstLinkWithDefault();
    }
}

void PXHyperlinkManager::GetPrevLinkWithDefault()
{
    // Do we already have a focus link?
    if (m_bSomeLinkHasKeyboardFocus)
    {
        // We already have a focus link
        //
        // Is it the default link?
        if (m_bDefaultLinkHasKeyboardFocus)
        {
            // Yes, the default link has focus,
            // so now we don't have any focus
            ClearKeyboardFocus();
        }
        else
        {
            // Some other link has focus
            //
            // Get the prev link
            PXHyperlink* pLink = NULL;
            if (GetPrevLink(m_pKeyboardFocusLink, pLink))
            {
                m_bSomeLinkHasKeyboardFocus    = TRUE;
                m_bDefaultLinkHasKeyboardFocus = FALSE;
                m_pKeyboardFocusLink           = pLink;
            }
            else if (m_bHaveDefault)
            {
                m_bSomeLinkHasKeyboardFocus    = TRUE;
                m_bDefaultLinkHasKeyboardFocus = TRUE;
                m_pKeyboardFocusLink           = NULL;
            }
            else
            {
                ClearKeyboardFocus();
            }
        }
    }
    else
    {
        // We don't currently have a focus link, so this is
        // the same as GetLastLinkWithDefault()
        GetLastLinkWithDefault();
    }
}
