/**************************************************************\
    FILE: address.cpp

    DESCRIPTION:
        The Class CAddressBand exists to support the Address
    ToolBand in either the main browser toolbar or as a
    ShellToolBand.
\**************************************************************/

#include "priv.h"
#include "sccls.h"
#include "addrlist.h"
#include "itbar.h"
#include "itbdrop.h"
#include "util.h"
#include "aclhist.h"
#include "aclmulti.h"
#include "autocomp.h"
#include "address.h"
#include "shellurl.h"
#include "resource.h"
#include "uemapp.h"
#include <tb_ids.h>
#include "apithk.h"

#include "mluisupp.h"

#define SUPERCLASS CToolBand
#define MIN_DROPWIDTH 200
const static TCHAR c_szAddressBandProp[]   = TEXT("CAddressBand_This");

//=================================================================
// Implementation of CAddressBand
//=================================================================

//===========================
// *** IUnknown Interface ***

HRESULT CAddressBand::QueryInterface(REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, IID_IWinEventHandler))
    {
        *ppvObj = SAFECAST(this, IWinEventHandler*);
    }
    else if (IsEqualIID(riid, IID_IAddressBand))
    {
        *ppvObj = SAFECAST(this, IAddressBand*);
    }
    else if (IsEqualIID(riid, IID_IPersistStream))
    {
        *ppvObj = SAFECAST(this, IPersistStream*);
    }
    else if (IsEqualIID(riid, IID_IServiceProvider))
    {
        *ppvObj = SAFECAST(this, IServiceProvider*);
    }
    else if (IsEqualIID(riid, IID_IInputObjectSite))
    {
        *ppvObj = SAFECAST(this, IInputObjectSite*);
    }
    else
    {
        return SUPERCLASS::QueryInterface(riid, ppvObj);
    }

    AddRef();
    return S_OK;
}


//================================
// *** IDockingWindow Interface ***
/****************************************************\
    FUNCTION: ShowDW

    DESCRIPTION:
        fShow == TRUE means show the window, FALSE means
    remove the window from the view.  The window will
    be created if needed.
\****************************************************/
HRESULT CAddressBand::ShowDW(BOOL fShow)
{
    if (!_hwnd)
        return S_FALSE; // The window needs to be created first.

    ShowWindow(_hwnd, fShow ? SW_SHOW : SW_HIDE);

    // Refresh if we are becoming visible because we could have
    // received and ignored FileSysChange() events while
    // we where hidden.
    if (fShow && !_fVisible)
        Refresh(NULL);

    _fVisible = BOOLIFY(fShow);
    return SUPERCLASS::ShowDW(fShow);
}


HRESULT CAddressBand::CloseDW(DWORD dw)
{
    if(_paeb)
        _paeb->Save(0);

    return SUPERCLASS::CloseDW(dw);
}



/****************************************************\
    FUNCTION: SetSite

    DESCRIPTION:
        This function will be called to have this
    Toolband try to obtain enough information about its
    parent Toolbar to create the Band window and maybe
    connect to a Browser Window.
\****************************************************/
HRESULT CAddressBand::SetSite(IUnknown *punkSite)
{
    HRESULT hr;
    BOOL fSameHost = punkSite == _punkSite;

    if (!punkSite && _paeb)
    {
        IShellService * pss;

        hr = _paeb->QueryInterface(IID_IShellService, (LPVOID *)&pss);
        if (SUCCEEDED(hr))
        {
            hr = pss->SetOwner(NULL);
            pss->Release();
        }
    }

    hr = SUPERCLASS::SetSite(punkSite);
    if (punkSite && !fSameHost)
    {
        hr = _CreateAddressBand(punkSite);
        // This call failing is expected when the host doesn't have a Browser Window.
    }

    // Set or reset the AddressEditBox's Browser IUnknown.
    if (_paeb)
    {
        IShellService * pss;

        hr = _paeb->QueryInterface(IID_IShellService, (LPVOID *)&pss);
        if (SUCCEEDED(hr))
        {
            // CAddressBand and the BandSite(host) have a ref count cycle.  This cycle
            // is broken when BandSite calls SetSite(NULL) which will cause
            // CAddressBand to break the cycle by releasing it's punk to the BandSite.
            //
            // CAddressEditBox and CAddressBand have the same method of breaking the
            // cycle.  This is accomplished by passing NULL to IAddressEditBox(NULL, NULL)
            // if our caller is breaking the cycle.  This will cause CAddressEditBox to
            // release it's ref count on CAddressBand.
            hr = pss->SetOwner((punkSite ? SAFECAST(this, IAddressBand *) : NULL));
            pss->Release();
        }
    }

    // setsite must succeed
    return S_OK;
}


//================================
// *** IInputObject Methods ***
HRESULT CAddressBand::TranslateAcceleratorIO(LPMSG lpMsg)
{
    BOOL fForwardToView = FALSE;
    static CHAR szAccel[2] = "\0"; // Alt-D needs to be localizable

    switch (lpMsg->message)
    {
    case WM_KEYDOWN:    // process these
        if (IsVK_TABCycler(lpMsg))
        {
            // If we are tabbing away, let the edit box know so
            // that it clears its dirty flag.
            SendMessage(_hwndEdit, WM_KEYDOWN, VK_TAB, 0);
        }
        else
        {
            fForwardToView = TRUE;
        }

        switch (lpMsg->wParam)
        {
            case VK_F1:     // help
            {
                //
                // FEATURE: Should add and accelerator for this and simply return S_FALSE, but that
                // causes two instances of the help dialog to come up when focus is in Trident.
                // This is the quick fix for IE5B2.
                //
                IOleCommandTarget* poct;
                IServiceProvider* psp;
                if (_punkSite && SUCCEEDED(_punkSite->QueryInterface(IID_IServiceProvider, (void**)&psp)))
                {
                    if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser, IID_IOleCommandTarget, (LPVOID*)&poct)))
                    {
                        poct->Exec(&CGID_ShellBrowser, DVIDM_HELPSEARCH, 0, NULL, NULL);
                        poct->Release();
                    }
                    psp->Release();
                }
                return S_FALSE;
            }
            case VK_F11:    // fullscreen
            {
                return S_FALSE;
            }

            case VK_F4:
            {
                if (_fVisible)
                {
                    if (HasFocusIO() == S_FALSE)
                        SetFocus(_hwnd);

                    // toggle the dropdown state
                    SendMessage(_hwnd, CB_SHOWDROPDOWN,
                                !SendMessage(_hwnd, CB_GETDROPPEDSTATE, 0, 0L), 0);

                    // Leave focus in the edit box so you can keep typing
                    if (_hwndEdit)
                        SetFocus(_hwndEdit);
                }
                else
                {
                    ASSERT(0); // Should this really be ignored?
                }

                return S_OK;
            }
            case VK_TAB:
            {
                // See if the editbox wants the tab character
                if (SendMessage(_hwndEdit, WM_GETDLGCODE, lpMsg->wParam, (LPARAM)lpMsg) == DLGC_WANTTAB)
                {
                    // We want the tab character
                    return S_OK;
                }
                break;
            }

            case VK_RETURN:
            {
                //
                // Ctrl-enter is used for quick complete, so pass it through
                //
                if (GetKeyState(VK_CONTROL) & 0x80000000)
                {
                    TranslateMessage(lpMsg);
                    DispatchMessage(lpMsg);
                    return S_OK;
                }
                break;
            }
        }
        break;
    case WM_KEYUP:      // eat any that WM_KEYDOWN processes
        switch (lpMsg->wParam)
        {
            case VK_F1:     // help
            case VK_F11:    // fullscreen
                return S_FALSE;

            case VK_RETURN:
            case VK_F4:
            case VK_TAB:
                return S_OK;
            default:
                break;
        }
        break;

    case WM_SYSCHAR:
        {
            CHAR   szChar [2] = "\0";
            if ('\0' == szAccel[0]) {
                MLLoadStringA(IDS_ADDRBAND_ACCELLERATOR,szAccel,ARRAYSIZE(szAccel));
            }
            szChar[0] = (CHAR)lpMsg->wParam;

            if (lstrcmpiA(szChar,szAccel) == 0)
            {
                ASSERT(_fVisible);
                if (_fVisible && (HasFocusIO() == S_FALSE))
                {
                    SetFocus(_hwnd);
                }
                return S_OK;
            }
        }
        break;

    case WM_SYSKEYUP:   // eat any that WM_SYSKEYDOWN processes
        if ('\0' == szAccel[0]) {
            MLLoadStringA(IDS_ADDRBAND_ACCELLERATOR,szAccel,ARRAYSIZE(szAccel));
        }

        if ((CHAR)lpMsg->wParam == szAccel[0]) {
            return S_OK;
        }
        break;
    }

    HRESULT hres = EditBox_TranslateAcceleratorST(lpMsg);

    if (hres == S_FALSE && fForwardToView)
    {
        IShellBrowser *psb;
        // we did not process this try the view before we return
        if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_STopLevelBrowser, IID_IShellBrowser, (void **)&psb)))
        {
            IShellView *psv;

            if (SUCCEEDED(psb->QueryActiveShellView(&psv)))
            {
                hres = psv->TranslateAccelerator(lpMsg);
                psv->Release();
            }
            psb->Release();
        }
    }

    return hres;
}


HRESULT CAddressBand::HasFocusIO()
{
    if ((_hwndEdit&& (GetFocus() == _hwndEdit)) ||
        SendMessage(_hwnd, CB_GETDROPPEDSTATE, 0, 0))
        return S_OK;

    return S_FALSE;
}


//=====================================
// *** IInputObjectSite Interface ***
HRESULT CAddressBand::OnFocusChangeIS(IUnknown *punk, BOOL fSetFocus)
{
    HRESULT hr;

    ASSERT(_punkSite);
    hr = IUnknown_OnFocusChangeIS(_punkSite, punk, fSetFocus);
    return hr;
}


//=====================================
// *** IOleCommandTarget Interface ***
HRESULT CAddressBand::QueryStatus(const GUID *pguidCmdGroup,
    ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext)
{
    ASSERT(_paeb);
    return IUnknown_QueryStatus(_paeb, pguidCmdGroup, cCmds, rgCmds, pcmdtext);
}


HRESULT CAddressBand::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt,
                        VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
{
    HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;

    if (pguidCmdGroup == NULL)
    {
        // nothing
    }
    else if (IsEqualGUID(CGID_Explorer, *pguidCmdGroup))
    {

        switch (nCmdID)
        {
        case SBCMDID_GETADDRESSBARTEXT:
            hr = S_OK;

            TCHAR wz[MAX_URL_STRING];
            UINT   cb = 0;
            BSTR   bstr = NULL;
            VariantInit(pvarargOut);

            if (_hwndEdit)
                cb = Edit_GetText(_hwndEdit, (TCHAR *)&wz, ARRAYSIZE(wz));
            if (cb)
                bstr = SysAllocStringLen(NULL, cb);
            if (bstr)
            {
                SHTCharToUnicode(wz, bstr, cb);
                pvarargOut->vt = VT_BSTR|VT_BYREF;
                pvarargOut->byref = bstr;
            }
            else
            {
                // VariantInit() might do this for us.
                pvarargOut->vt = VT_EMPTY;
                pvarargOut->byref = NULL;
                return E_FAIL;   // Edit_GetText gave us nothing
            }
            break;
        }
    }
    else if (IsEqualGUID(CGID_DeskBand, *pguidCmdGroup))
    {
        switch (nCmdID)
        {
        case DBID_SETWINDOWTHEME:
            if (pvarargIn && pvarargIn->vt == VT_BSTR)
            {
                if (_hwnd)
                {
                    Comctl32_SetWindowTheme(_hwnd, pvarargIn->bstrVal);
                    Comctl32_SetWindowTheme(_hwndTools, pvarargIn->bstrVal);
                    _BandInfoChanged();
                }
            }

            hr = S_OK;
            break;
        }
    }

    if (FAILED(hr))
    {
        hr = IUnknown_Exec(_paeb, pguidCmdGroup, nCmdID, nCmdexecopt, pvarargIn, pvarargOut);
    }

    return(hr);
}

extern HRESULT IsDesktopBrowser(IUnknown *punkSite);

//================================
// *** IDeskBand Interface ***
/****************************************************\
    FUNCTION: GetBandInfo

    DESCRIPTION:
        This function will give the caller information
    about this Band, mainly the size of it.
\****************************************************/
HRESULT CAddressBand::GetBandInfo(DWORD dwBandID, DWORD fViewMode,
                                DESKBANDINFO* pdbi)
{
    HRESULT hr  = S_OK;

    _dwBandID = dwBandID;
    _fVertical = ((fViewMode & (DBIF_VIEWMODE_VERTICAL | DBIF_VIEWMODE_FLOATING)) != 0);

    pdbi->dwModeFlags = DBIMF_FIXEDBMP;

    pdbi->ptMinSize.x = 0;
    pdbi->ptMinSize.y = 0;
    if (_fVertical) {
        pdbi->ptMinSize.y = GetSystemMetrics(SM_CXSMICON);
        pdbi->ptMaxSize.y = -1; // random
        pdbi->ptIntegral.y = 1;
        pdbi->dwModeFlags |= DBIMF_VARIABLEHEIGHT;
    } else {
        if (_hwnd) {
            HWND hwndCombo;
            RECT rcCombo;

            hwndCombo = (HWND)SendMessage(_hwnd, CBEM_GETCOMBOCONTROL, 0, 0);
            ASSERT(hwndCombo);
            GetWindowRect(hwndCombo, &rcCombo);
            pdbi->ptMinSize.y = RECTHEIGHT(rcCombo);
        }
        ASSERT(pdbi->ptMinSize.y < 200);

    }

    MLLoadStringW(IDS_BAND_ADDRESS2, pdbi->wszTitle, ARRAYSIZE(pdbi->wszTitle));
    if (IsDesktopBrowser(_punkSite) != S_FALSE) {
        // non- shell browser host (e.g. desktop or tray)
        //
        // this is slightly (o.k., very) hoaky.  the only time we want to
        // show a mnemonic is when we're in a browser app.  arguably we
        // should generalize this to all bands/bandsites by having a
        // DBIMF_WITHMNEMONIC or somesuch, but that would mean adding a
        // CBandSite::_dwModeFlag=0 and overriding it in itbar::CBandSite.
        // that seems like a lot of work for a special case so instead we
        // hack it in here based on knowledge of our host.
        TraceMsg(DM_TRACE, "cab.gbi: nuke Address mnemonic");
        MLLoadStringW(IDS_BAND_ADDRESS, pdbi->wszTitle, ARRAYSIZE(pdbi->wszTitle));
    }

    return hr;
}

//================================
//  ** IWinEventHandler Interface ***
/****************************************************\
    FUNCTION: OnWinEvent

    DESCRIPTION:
        This function will give receive events from
    the parent ShellToolbar.
\****************************************************/
HRESULT CAddressBand::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plres)
{
    switch (uMsg)
    {
    case WM_WININICHANGE:
        if (SHIsExplorerIniChange(wParam, lParam) & (EICH_KINET | EICH_KINETMAIN))
        {
            _InitGoButton();
        }

        if (wParam == SPI_SETNONCLIENTMETRICS)
        {
            // Tell the combobox so that it can update its font
            SendMessage(_hwnd, uMsg, wParam, lParam);

            // Inform the band site that our height may have changed
            _BandInfoChanged();
        }
        break;

    case WM_COMMAND:
        {
            UINT idCmd = GET_WM_COMMAND_ID(wParam, lParam);
            if (idCmd == FCIDM_VIEWGOBUTTON)
            {
                // Toggle the go-button visibility
                BOOL fShowGoButton = !SHRegGetBoolUSValue(REGSTR_PATH_MAIN,
                    TEXT("ShowGoButton"), FALSE, /*default*/TRUE);

                SHRegSetUSValue(REGSTR_PATH_MAIN,
                            TEXT("ShowGoButton"),
                            REG_SZ,
                            (LPVOID)(fShowGoButton ? L"yes" : L"no"),
                            (fShowGoButton ? 4 : 3)*sizeof(TCHAR),
                            SHREGSET_FORCE_HKCU);

                // Tell the world that something has changed
                SendShellIEBroadcastMessage(WM_WININICHANGE, 0, (LPARAM)REGSTR_PATH_MAIN, 3000);
            }
        }
    }

    if (_pweh)
        return _pweh->OnWinEvent(_hwnd, uMsg, wParam, lParam, plres);
    else
        return S_OK;
}


/****************************************************\
    FUNCTION: IsWindowOwner

    DESCRIPTION:
        This function will return TRUE if the HWND
    passed in is a HWND owned by this band.
\****************************************************/
HRESULT CAddressBand::IsWindowOwner(HWND hwnd)
{
    if (_pweh)
        return _pweh->IsWindowOwner(hwnd);
    else
        return S_FALSE;
}


//================================
// *** IAddressBand Interface ***
/****************************************************\
    FUNCTION: FileSysChange

    DESCRIPTION:
        This function will handle file system change
    notifications.
\****************************************************/
HRESULT CAddressBand::FileSysChange(DWORD dwEvent, LPCITEMIDLIST * ppidl)
{
    HRESULT hr = S_OK;

    if (_fVisible)
    {
        hr = IUnknown_FileSysChange(_paeb, dwEvent, ppidl);
    }
    return hr;
}


/****************************************************\
    FUNCTION: Refresh

    PARAMETERS:
        pvarType - NULL for a refress of everything.
                   OLECMD_REFRESH_TOPMOST will only update the top most.

    DESCRIPTION:
        This function will force a refress of part
    or all of the AddressBand.
\****************************************************/
HRESULT CAddressBand::Refresh(VARIANT * pvarType)
{
    HRESULT hr = S_OK;
    IAddressBand * pab;

    if (_paeb)
    {
        hr = _paeb->QueryInterface(IID_IAddressBand, (LPVOID *)&pab);
        if (SUCCEEDED(hr))
        {
            hr = pab->Refresh(pvarType);
            pab->Release();
        }
    }

    return hr;
}

/****************************************************\
    Address Band Constructor
\****************************************************/
CAddressBand::CAddressBand()
{
    TraceMsg(TF_SHDLIFE, "ctor CAddressBand %x", this);

    // This needs to be allocated in Zero Inited Memory.
    // ASSERT that all Member Variables are inited to Zero.
    ASSERT(!_hwndEdit);
    ASSERT(!_paeb);
    ASSERT(!_pweh);

    _fCanFocus = TRUE;      // we accept focus (see CToolBand::UIActivateIO)
}


/****************************************************\
    Address Band destructor
\****************************************************/
CAddressBand::~CAddressBand()
{
    ATOMICRELEASE(_paeb);
    ATOMICRELEASE(_pweh);

    //
    // Make sure the toolbar is destroyed before we free
    // the image lists
    //
    if (_hwndTools && IsWindow(_hwndTools))
    {
        DestroyWindow(_hwndTools);
    }
    if (_himlDefault) ImageList_Destroy(_himlDefault);
    if (_himlHot)  ImageList_Destroy(_himlHot);

    //
    // Our window must be destroyed before we are freed
    // so that the window doesn't try to reference us.
    //
    if (_hwnd && IsWindow(_hwnd))
    {
        DestroyWindow(_hwnd);

        // Null out base classes window handle because
        // its destructor is next
        _hwnd = NULL;
    }

    TraceMsg(TF_SHDLIFE, "dtor CAddressBand %x", this);
}


/****************************************************\
    FUNCTION: CAddressBand_CreateInstance

    DESCRIPTION:
        This function will create an instance of the
    AddressBand COM object.
\****************************************************/
HRESULT CAddressBand_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
    // aggregation checking is handled in class factory

    *ppunk = NULL;
    CAddressBand * p = new CAddressBand();
    if (p)
    {
        *ppunk = SAFECAST(p, IDeskBand *);
        return NOERROR;
    }

    return E_OUTOFMEMORY;
}


/****************************************************\
    FUNCTION: _CreateAddressBand

    DESCRIPTION:
        This function will create the AddressBand window
    with the ComboBox.
\****************************************************/
HRESULT CAddressBand::_CreateAddressBand(IUnknown * punkSite)
{
    HRESULT hr = S_OK;

    if (_hwnd)
    {
        IShellService * pss;

        if (_hwndTools)
        {
            DestroyWindow(_hwndTools);
            _hwndTools = NULL;
        }

        DestroyWindow(_hwnd);
        _hwnd = NULL;

        ASSERT(_punkSite);
        if (_paeb)
        {
            hr = _paeb->QueryInterface(IID_IShellService, (LPVOID *)&pss);
            if (SUCCEEDED(hr))
            {
                hr = pss->SetOwner(NULL);
                pss->Release();
            }
        }
        ATOMICRELEASE(_paeb);
        ATOMICRELEASE(_pweh);
    }

    //
    // Create address window.
    //

    ASSERT(_hwndParent);        // Call us after SetSite()
    if (!_hwndParent)
    {
            // The caller hasn't called SetSite(), so we can't
            // create our window because we can't find out our parent's
            // HWND.
            return E_FAIL;
    }
    _InitComCtl32();    // don't check result, if this fails our CreateWindows will fail


    DWORD dwWindowStyles = WS_TABSTOP | WS_CHILD | WS_CLIPCHILDREN | WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL;

    // WARNING: MSN and other Rooted Explorers may not have implemented all
    // of the ParseDisplayName and other IShellFolder members
    // If we want to continue to support MSN, we will need to turn on the
    // CBS_DROPDOWNLIST if ISROOTEDCLASS() and the clsid is equal to the MSN clsid.

    // dwWindowStyles |= CBS_DROPDOWNLIST; // (This turns off the ComboBox's Editbox)

    DWORD dwExStyle = WS_EX_TOOLWINDOW;

    if (IS_WINDOW_RTL_MIRRORED(_hwndParent))
    {
        // If the parent window is mirrored then the ComboBox window will inheret the mirroring flag
        // And we need the reading order to be Left to right, which is the right to left in the mirrored mode.
        dwExStyle |= WS_EX_RTLREADING;
    }

    _hwnd = CreateWindowEx(dwExStyle, WC_COMBOBOXEX, NULL, dwWindowStyles,
                           0, 0, 100, 250, _hwndParent,
                           (HMENU) FCIDM_VIEWADDRESS, HINST_THISDLL, NULL);

    if (_hwnd)
    {
        // Initial combobox parameters.
        SendMessage(_hwnd, CBEM_SETEXTENDEDSTYLE,
                CBES_EX_NOSIZELIMIT | CBES_EX_CASESENSITIVE,
                CBES_EX_NOSIZELIMIT | CBES_EX_CASESENSITIVE);

        // NOTE: _hwndEdit will be NULL if the CBS_DROPDOWNLIST flag has been turned on
        _hwndEdit  = (HWND)SendMessage(_hwnd, CBEM_GETEDITCONTROL, 0, 0L);
        _hwndCombo = (HWND)SendMessage(_hwnd, CBEM_GETCOMBOCONTROL, 0, 0L);

        // Subclass the Edit control's procedure to handle ModeBias issue.
        if ( _hwndEdit  && SetProp(_hwndEdit, c_szAddressBandProp, this))
        {
           _pfnOldEditProc = (WNDPROC) SetWindowLongPtr(_hwndEdit, GWLP_WNDPROC, (LONG_PTR) _ComboExEditProc);
        }

        ASSERT(!_paeb && !_pweh);
        hr = CoCreateInstance(CLSID_AddressEditBox, NULL, CLSCTX_INPROC_SERVER, IID_IAddressEditBox, (void **)&_paeb);
        // If this object fails to initialize, it won't work!!!  Make sure you REGSVR32ed and RUNDLL32ed shdocvw.dll
        if (SUCCEEDED(hr))
        {
            hr = _paeb->QueryInterface(IID_IWinEventHandler, (LPVOID *)&_pweh);
            ASSERT(SUCCEEDED(hr));
            hr = _paeb->Init(_hwnd, _hwndEdit, AEB_INIT_AUTOEXEC, SAFECAST(this, IAddressBand *));
        }

        // Create the go button if it's enabled
        _InitGoButton();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}


//================================
// *** IPersistStream Interface ***

/****************************************************\
    FUNCTION: Load

    DESCRIPTION:
        This function will currently only persist the
    CAddressEditBox object.

    HISTORY:
    Ver 1: Contains the CAddressEditBox::Save() stream.
\****************************************************/
#define STREAM_VERSION_CADDRESSBAND      0x00000001

HRESULT CAddressBand::Load(IStream *pstm)
{
    HRESULT hr;
    DWORD dwSize;
    DWORD dwVersion;

    hr = LoadStreamHeader(pstm, STREAMHEADER_SIG_CADDRESSBAND, STREAM_VERSION_CADDRESSBAND,
        STREAM_VERSION_CADDRESSBAND, &dwSize, &dwVersion);
    ASSERT(SUCCEEDED(hr));

    if (S_OK == hr)
    {
        switch (dwVersion)
        {
        case 1:     // Ver 1.
            // Nothing.
            break;
        default:
            ASSERT(0);  // Should never get here.
            break;
        }
    }
    else if (S_FALSE == hr)
        hr = S_OK;  // We already have our default data set.

    return hr;
}


/****************************************************\
    FUNCTION: Save

    DESCRIPTION:
        This function will currently only persist the
    CAddressEditBox object.

    HISTORY:
    Ver 1: Contains the CAddressEditBox::Save() stream.
\****************************************************/
HRESULT CAddressBand::Save(IStream *pstm, BOOL fClearDirty)
{
    HRESULT hr;

    hr = SaveStreamHeader(pstm, STREAMHEADER_SIG_CADDRESSBAND,
                STREAM_VERSION_CADDRESSBAND, 0);
    ASSERT(SUCCEEDED(hr));

    if (SUCCEEDED(hr))
    {
        IPersistStream * pps;

        ASSERT(_paeb);
        if (_paeb)
        {
            hr = _paeb->QueryInterface(IID_IPersistStream, (LPVOID *)&pps);
            if(EVAL(SUCCEEDED(hr)))
            {
                hr = pps->Save(pstm, fClearDirty);
                pps->Release();
            }
        }
    }

    return hr;
}


void CAddressBand::_OnGetInfoTip(LPNMTBGETINFOTIP pnmTT)
{
    // Format a tooltip: "go to <contents of address bar>"
    WCHAR szAddress[MAX_PATH];
    if (GetWindowText(_hwndEdit, szAddress, ARRAYSIZE(szAddress)))
    {
        WCHAR szFormat[MAX_PATH];
        const int MAX_TOOLTIP_LENGTH = 100;
        int cchMax = (pnmTT->cchTextMax < MAX_TOOLTIP_LENGTH) ? pnmTT->cchTextMax : MAX_TOOLTIP_LENGTH;

        MLLoadString(IDS_GO_TOOLTIP, szFormat, ARRAYSIZE(szFormat));
        int cch = wnsprintf(pnmTT->pszText, cchMax, szFormat, szAddress);

        // Append ellipses?
        if (cch == cchMax - 1)
        {
            // Note that Japan has a single character for ellipses, so we load
            // as a resource.
            WCHAR szEllipses[10];
            cch = MLLoadString(IDS_ELLIPSES, szEllipses, ARRAYSIZE(szEllipses));
            StrCpyN(pnmTT->pszText + cchMax - cch - 1, szEllipses, cch + 1);
        }
    }
    else if (pnmTT->cchTextMax > 0)
    {
        // Use button text for tooltip
        *pnmTT->pszText = L'\0';
    }
}

//+-------------------------------------------------------------------------
// Subclassed window procedure of the combobox Edit control in the address band
//--------------------------------------------------------------------------
LRESULT CALLBACK CAddressBand::_ComboExEditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CAddressBand* pThis = (CAddressBand*)GetProp(hwnd, c_szAddressBandProp);

    if (!pThis)
        return DefWindowProcWrap(hwnd, uMsg, wParam, lParam);

    WNDPROC pfnOldEditProc = pThis->_pfnOldEditProc;

    switch (uMsg)
    {
    case WM_KILLFOCUS :

        SetModeBias(MODEBIASMODE_DEFAULT);
        break;

    case WM_SETFOCUS:

        SetModeBias(MODEBIASMODE_URLHISTORY);
        break;

    case WM_DESTROY:
        //
        // Unsubclass myself.
        //
        RemoveProp(hwnd, c_szAddressBandProp);
        if (pfnOldEditProc)
        {
            SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) pfnOldEditProc);
            pThis->_pfnOldEditProc = NULL;
        }
        break;
    default:
        break;
    }

    return CallWindowProc(pfnOldEditProc, hwnd, uMsg, wParam, lParam);
}

//+-------------------------------------------------------------------------
// Subclassed window procedure of the combobox in the address band
//--------------------------------------------------------------------------
LRESULT CALLBACK CAddressBand::_ComboExWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CAddressBand* pThis = (CAddressBand*)GetProp(hwnd, c_szAddressBandProp);

    if (!pThis)
        return DefWindowProcWrap(hwnd, uMsg, wParam, lParam);

    WNDPROC pfnOldWndProc = pThis->_pfnOldWndProc;

    switch (uMsg)
    {
    case WM_NOTIFYFORMAT:
        if (NF_QUERY == lParam)
        {
            return (DLL_IS_UNICODE ? NFR_UNICODE : NFR_ANSI);
        }
        break;

    case WM_WINDOWPOSCHANGING:
        {
            // Break out if the go button is hidden
            if (!pThis->_fGoButton)
                break;

            //
            // Make room for the go button on the right side
            //
            LPWINDOWPOS pwp = (LPWINDOWPOS)lParam;
            pwp->flags |= SWP_NOCOPYBITS;

            WINDOWPOS wp = *(LPWINDOWPOS)lParam;

            // Get the dimensions of our 'go' button
            RECT rc;
            SendMessage(pThis->_hwndTools, TB_GETITEMRECT, 0, (LPARAM)&rc);
            int cxGo = RECTWIDTH(rc);
            int cyGo = RECTHEIGHT(rc);

            // Make room for the go button on the right side
            wp.cx -= cxGo + 2;
            CallWindowProc(pfnOldWndProc, hwnd, uMsg, wParam, (LPARAM)&wp);

            // Paint underneath the 'go' button
            RECT rcGo = {wp.cx, 0, wp.cx + cxGo + 2, wp.cy};
            InvalidateRect(pThis->_hwnd, &rcGo, TRUE);

            // The outer window can be much higher than the internal combobox.
            // We want to center the go button on the combobox
            int y;
            if (pThis->_hwndCombo)
            {
                // Center vertically with inner combobox
                RECT rcCombo;
                GetWindowRect(pThis->_hwndCombo, &rcCombo);
                y = (rcCombo.bottom - rcCombo.top - cyGo)/2;
            }
            else
            {
                y = (wp.cy - cyGo)/2;
            }

            // Position the 'go' button on the right.  Note that the height will always be ok
            // because the addressbar displays 16x16 icons within it.
            SetWindowPos(pThis->_hwndTools, NULL, wp.cx + 2, y, cxGo, cyGo, SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOOWNERZORDER);

            // Adjust the drop-down width
            SendMessage(pThis->_hwndCombo, CB_SETDROPPEDWIDTH, MIN_DROPWIDTH, 0L);
            return 0;
        }
    case WM_SIZE:
        {
            // Break out if the go button is hidden
            if (!pThis->_fGoButton)
                break;

            //
            // Make room for the go button on the right side
            //
            int cx = LOWORD(lParam);
            int cy = HIWORD(lParam);

            // Get the dimensions of our 'go' button
            RECT rc;
            SendMessage(pThis->_hwndTools, TB_GETITEMRECT, 0, (LPARAM)&rc);
            int cxGo = RECTWIDTH(rc);
            int cyGo = RECTHEIGHT(rc);

            // Make room for the go button on the right side
            LPARAM lParamTemp = MAKELONG(cx - cxGo - 2, cy);
            CallWindowProc(pfnOldWndProc, hwnd, uMsg, wParam, lParamTemp);

            // Paint underneath the 'go' button
            RECT rcGo = {cx-cxGo, 0, cx, cy};
            InvalidateRect(pThis->_hwnd, &rcGo, TRUE);

            // The outer window can be much higher than the internal combobox.
            // We want to center the go button on the combobox
            int y;
            if (pThis->_hwndCombo)
            {
                // Center vertically with inner combobox
                RECT rcCombo;
                GetWindowRect(pThis->_hwndCombo, &rcCombo);
                y = (rcCombo.bottom - rcCombo.top - cyGo)/2;
            }
            else
            {
                y = (cy - cyGo)/2;
            }

            // Position the 'go' button on the right.  Note that the height will always be ok
            // because the addressbar displays 16x16 icons within it.
            SetWindowPos(pThis->_hwndTools, NULL, cx - cxGo, y, cxGo, cyGo, SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOOWNERZORDER);

            // Adjust the drop-down width
            SendMessage(pThis->_hwndCombo, CB_SETDROPPEDWIDTH, MIN_DROPWIDTH, 0L);
            return 0;
        }
    case WM_NOTIFY:
        {
            LPNMHDR pnm = (LPNMHDR)lParam;
            if (pnm->hwndFrom == pThis->_hwndTools)
            {
                switch (pnm->code)
                {
                case NM_CLICK:
                    // Simulate an enter key press in the combobox
                    SendMessage(pThis->_hwndEdit, WM_KEYDOWN, VK_RETURN, 0);
                    SendMessage(pThis->_hwndEdit, WM_KEYUP, VK_RETURN, 0);
                    // n.b. we also got a NAVADDRESS from the simulate
                    UEMFireEvent(&UEMIID_BROWSER, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_NAVIGATE, UIBL_NAVGO);
                    break;

                case NM_TOOLTIPSCREATED:
                {
                    //
                    // Make the tooltip show up even when the app is nit active
                    //
                    NMTOOLTIPSCREATED* pnmTTC = (NMTOOLTIPSCREATED*)pnm;
                    SHSetWindowBits(pnmTTC->hwndToolTips, GWL_STYLE, TTS_ALWAYSTIP | TTS_TOPMOST | TTS_NOPREFIX, TTS_ALWAYSTIP | TTS_TOPMOST | TTS_NOPREFIX);
                }
                break;
                case TBN_GETINFOTIP:
                    pThis->_OnGetInfoTip((LPNMTBGETINFOTIP)pnm);
                    break;
                }
                return 0;
            }
            break;
        }
    case WM_ERASEBKGND:
        {
            // Break out if the go button is hidden
            if (!pThis->_fGoButton)
                break;

            //
            // Forward the erase background to the parent so that
            // we appear transparent under the go button
            //
            HDC hdc = (HDC)wParam;
            HWND hwndParent = GetParent(hwnd);
            LRESULT lres = 0;

            if (hwndParent)
            {
                // Adjust the origin so the parent paints in the right place
                POINT pt = {0,0};

                MapWindowPoints(hwnd, hwndParent, &pt, 1);
                OffsetWindowOrgEx(hdc,
                                  pt.x,
                                  pt.y,
                                  &pt);

                lres = SendMessage(hwndParent, WM_ERASEBKGND, (WPARAM)hdc, 0L);

                SetWindowOrgEx(hdc, pt.x, pt.y, NULL);
            }

            if (lres != 0)
            {
                // We handled it
                return lres;
            }

            break;
         }

    case WM_DESTROY:
        //
        // Unsubclass myself.
        //
        RemoveProp(hwnd, c_szAddressBandProp);
        if (pfnOldWndProc)
        {
            SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) pfnOldWndProc);
            pThis->_pfnOldWndProc = NULL;
        }
        break;
    default:
        break;
    }

    return CallWindowProc(pfnOldWndProc, hwnd, uMsg, wParam, lParam);
}

//+-------------------------------------------------------------------------
// Creates and shows the go button
//--------------------------------------------------------------------------
BOOL CAddressBand::_CreateGoButton()
{
    ASSERT(_hwndTools == NULL);

    BOOL fRet = FALSE;
    BOOL bUseClassicGlyphs = SHUseClassicToolbarGlyphs();
    COLORREF crMask = RGB(255, 0, 255);

    if (_himlDefault == NULL)
    {
        if (bUseClassicGlyphs)
        {
            _himlDefault = ImageList_LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDB_GO), 16, 0, crMask,
                                                   IMAGE_BITMAP, LR_CREATEDIBSECTION);

        }
        else
        {
            _himlDefault = ImageList_LoadImage(GetModuleHandle(TEXT("shell32.dll")), MAKEINTRESOURCE(IDB_TB_GO_DEF_20), 20, 0, crMask,
                                                   IMAGE_BITMAP, LR_CREATEDIBSECTION);
        }
    }
    if (_himlHot == NULL)
    {
        if (bUseClassicGlyphs)
        {
            _himlHot  = ImageList_LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDB_GOHOT), 16, 0, crMask,
                                               IMAGE_BITMAP, LR_CREATEDIBSECTION);
        }
        else
        {
            _himlHot  = ImageList_LoadImage(GetModuleHandle(TEXT("shell32.dll")), MAKEINTRESOURCE(IDB_TB_GO_HOT_20), 20, 0, crMask,
                                               IMAGE_BITMAP, LR_CREATEDIBSECTION);
        }
    }

    // If we have the image lists, go ahead and create the toolbar control for the go button
    if (_himlDefault && _himlHot)
    {
        //
        // Subclass the comboboxex so that we can place the go botton within it.  The toolbad class
        // assumes one window per band, so this trick allows us to add the button using existing windows.
        // Note that comboex controls have a separate window used to wrap the internal combobox.  This
        // is the window that we use to host our "go" button.  We must subclass before creating the
        // go button so that we respond to WM_NOTIFYFORMAT with NFR_UNICODE.
        //
        //
        if (SetProp(_hwnd, c_szAddressBandProp, this))
        {
           _pfnOldWndProc = (WNDPROC) SetWindowLongPtr(_hwnd, GWLP_WNDPROC, (LONG_PTR) _ComboExWndProc);
        }

        // Create the toolbar control for the go button
        _hwndTools = CreateWindowEx(WS_EX_TOOLWINDOW, TOOLBARCLASSNAME, NULL,
                                WS_CHILD | TBSTYLE_FLAT |
                                TBSTYLE_TOOLTIPS |
                                TBSTYLE_LIST |
                                WS_CLIPCHILDREN |
                                WS_CLIPSIBLINGS | CCS_NODIVIDER | CCS_NOPARENTALIGN |
                                CCS_NORESIZE,
                                0, 0, 0, 0, _hwnd, NULL, HINST_THISDLL, NULL);
    }

    if (_hwndTools)
    {
        // Init the toolbar control
        SendMessage(_hwndTools, TB_BUTTONSTRUCTSIZE, SIZEOF(TBBUTTON), 0);
        SendMessage(_hwndTools, TB_SETMAXTEXTROWS, 1, 0L);
        SendMessage(_hwndTools, TB_SETBUTTONWIDTH, 0, (LPARAM) MAKELONG(0, 500));
        SendMessage(_hwndTools, TB_SETIMAGELIST, 0, (LPARAM)_himlDefault);
        SendMessage(_hwndTools, TB_SETHOTIMAGELIST, 0, (LPARAM)_himlHot);

        LRESULT nRet = SendMessage(_hwndTools, TB_ADDSTRING, (WPARAM)MLGetHinst(), (LPARAM)IDS_ADDRESS_TB_LABELS);
        ASSERT(nRet == 0);

        static const TBBUTTON tbb[] =
        {
            {0, 1, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, 0},
        };
        SendMessage(_hwndTools, TB_ADDBUTTONS, ARRAYSIZE(tbb), (LPARAM)tbb);

        fRet = TRUE;
    }
    else
    {
        // If no toolbar control, don't subclass the comboboxex
        if (_pfnOldWndProc)
        {
            RemoveProp(_hwnd, c_szAddressBandProp);
            SetWindowLongPtr(_hwnd, GWLP_WNDPROC, (LONG_PTR) _pfnOldWndProc);
            _pfnOldWndProc = NULL;
        }
    }

    return fRet;
}


//+-------------------------------------------------------------------------
// Shows/hides the go button depending on the current registry settings
//--------------------------------------------------------------------------
void CAddressBand::_InitGoButton()
{
    BOOL fUpdate = FALSE;
    //
    // Create the go button if it's enabled
    //
    // down-level client fix: only show Go in shell areas when NT5 or greater
    // or on a window that was originally IE

    BOOL fShowGoButton = SHRegGetBoolUSValue(REGSTR_PATH_MAIN,
        TEXT("ShowGoButton"), FALSE, /*default*/TRUE)
        && (WasOpenedAsBrowser(_punkSite) || GetUIVersion() >= 5);

    if (fShowGoButton && (_hwndTools || _CreateGoButton()))
    {
        ShowWindow(_hwndTools, SW_SHOW);
        _fGoButton = TRUE;
        fUpdate = TRUE;
    }
    else if (_hwndTools && IsWindowVisible(_hwndTools))
    {
        ShowWindow(_hwndTools, SW_HIDE);
        _fGoButton = FALSE;
        fUpdate = TRUE;
    }

    // If the go button was hidden or shown, get the combobox to adjust itself
    if (fUpdate)
    {
        // Resetting the item height gets the combobox to update the size of the editbox
        LRESULT iHeight = SendMessage(_hwnd, CB_GETITEMHEIGHT, -1, 0);
        if (iHeight != CB_ERR)
        {
            SendMessage(_hwnd, CB_SETITEMHEIGHT, -1, iHeight);
        }
    }
}
