#include "priv.h"
#include "sccls.h"
#include "resource.h"
#include "mshtmhst.h"
#include "desktopp.h"   // DTRF_RAISE etc.

#define WANT_CBANDSITE_CLASS
#include "bandsite.h"

#include "deskbar.h"
#include "theater.h"

#include "mluisupp.h"

#define SUPERCLASS CBaseBar

#define DM_PERSIST      0               // trace IPS::Load, ::Save, etc.
#define DM_POPUI        0               // expando-UI (proto)
#define DM_MENU         0               // trace menu code
#define DM_DRAG         0               // drag move/size (terse)
#define DM_DRAG2        0               // ... (verbose)
#define DM_API          0               // trace API calls
#define DM_HIDE         DM_TRACE               // autohide
#define DM_HIDE2        DM_TRACE               // autohide (verbose)
#define DM_APPBAR       0               // SHAppBarMessage calls
#define DM_OLECT        0               // IOleCommandTarget calls
#define DM_FOCUS        0               // focus change
#define DM_RES          DM_WARNING      // resolution

#define ABS(i)  (((i) < 0) ? -(i) : (i))

#define RECTGETWH(uSide, prc)   (ABE_HORIZ(uSide) ? RECTHEIGHT(*prc) : RECTWIDTH(*prc))

//***   CDB_INITED -- has CDockingBar::_Initialize been called
//
#define CDB_INITED()   (_eInitLoaded && _fInitSited && _fInitShowed)

enum ips_e {
    IPS_FALSE,    // reserved, must be 0 (FALSE)
    IPS_LOAD,
    IPS_LOADBAG,
    IPS_INITNEW,
    IPS_LAST
};

CASSERT(IPS_FALSE == 0);
CASSERT(((IPS_LAST - 1) & 0x03) == (IPS_LAST - 1)); // 2-bit _eInitLoaded


//***   CXFLOAT -- distance from edge to 'float' zone 
// NOTES
//  pls forgive the lousy hungarian...
#define CXFLOAT()   GetSystemMetrics(SM_CXICON)
#define CYFLOAT()   GetSystemMetrics(SM_CYICON)
#define CXYHIDE(uSide)  2       // FEATUE: GetSystemMetrics(xxx), we need an appropriate system metric

#ifdef DEBUG
#if 0   // turn on to debug autohide boundary cases
int g_cxyHide = 8;
#undef  CXYHIDE
#define CXYHIDE(uSide)  g_cxyHide
#endif
#endif

#define CXSMSIZE()  GetSystemMetrics(SM_CXSMSIZE)
#define CYSMSIZE()  GetSystemMetrics(SM_CYSMSIZE)


#ifdef DEBUG
extern unsigned long DbStreamTell(IStream *pstm);
extern BOOL DbCheckWindow(HWND hwnd, RECT *prcExp, HWND hwndClient);
TCHAR *DbMaskToMneStr(UINT uMask, TCHAR *szMnemonics);
#else
#define DbStreamTell(pstm) 0
#define DbCheckWindow(hwnd, prcExp, hwndClient) 0
#define DbMaskToMneStr(uMask, szMnemonics) szMnemonics
#endif

//***   autohide -- design note
//
// here's an overview of how we do autohide.  see the code for details.
//
// only a few routines really know about it.  their behavior is driven
// by '_fHiding'.  when FALSE, they behave normally.  when TRUE, they
// do alternate 'fake' behavior.
//
// a 'real' or 'normal' rect is the full-size rect we display when not hidden.
// a 'fake' or 'tiny' rect is the very thin rect we display when hidden.
// (plus there's a '0-width' rect we register w/ the system when we're hidden).
//
// more specifically,
//
// when fHiding is TRUE, a few routines have alternate 'fake' behavior:
//      _ProtoRect      returns a 'tiny' rect rather than the 'real' rect
//      _NegotiateRect  is a NOOP (so we don't change the 'tiny' rect) 
//      _SetVRect       is a NOOP (so we don't save   the 'tiny' rect)
//      AppBarSetPos    is a NOOP (so we don't set    the 'tiny' rect)
// plus, a few routines handle transitions (and setup):
//      _DoHide         hide/unhide helper
//      _MoveSizeHelper detects and handles transitions
//      _HideReg        register autohide appbar w/ 0-width rect
// and finally, a few messages trigger the transitions:
//      unhide          WM_NCHITTEST on the 'tiny' rect starts the unhide.
//              actually it starts a timer (IDT_AUTOUNHIDE) so there's a
//              bit of hysteresis.
//      hide            WM_ACTIVATE(deact) starts a timer (IDT_AUTOHIDE)
//              which we use to poll for mouse leave events.  again, there
//              is some hysteresis, plus some additional heuristics for hiding.
//              WM_ACTIVATE(act) stops the timer.
//
// #if 0
// we also have 'manual hide'.  manual hide differs from autohide as follows:
//     autohide never negotiates space(*)   , manual hide always does
//     autohide is focus- and cursor- driven, manual hide is UI-driven
//     (*) actually it negotiates space of '0'.
// e.g. 'manual hide' is used for the BrowserBar (e.g. search results).
// however for now at least 'manual hide' is simply a ShowDW(FALSE).
// #endif

void CDockingBar::_AdjustToChildSize()
{
    if (_szChild.cx)
    {
        RECT rc, rcChild;
    
        GetWindowRect(_hwnd, &rc);
        GetClientRect(_hwndChild, &rcChild);

        // we need to change rc by the delta of prc-rcChild
        rc.right += _szChild.cx - RECTWIDTH(rcChild);
        rc.bottom += _szChild.cy - RECTHEIGHT(rcChild);

        _SetVRect(&rc);
    
        _Recalc();  // _MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);

        _szChild.cx = 0;
    }
}

void CDockingBar::_OnPostedPosRectChange()
{
    if (_ptbSite) {
        if (!_fDragging) {
            _AdjustToChildSize();
        }
    }
}

HMENU CDockingBar::_GetContextMenu()
{
    HMENU hmenu = LoadMenuPopup(MENU_WEBBAR);
    if (hmenu) {

        // _eMode
        if (!ISWBM_DESKTOP())
        {
            EnableMenuItem(hmenu, IDM_AB_TOPMOST, MF_BYCOMMAND | MF_GRAYED);
            EnableMenuItem(hmenu, IDM_AB_AUTOHIDE, MF_BYCOMMAND | MF_GRAYED);
        }
        CheckMenuItem(hmenu, IDM_AB_TOPMOST, WBM_IS_TOPMOST() ? (MF_BYCOMMAND | MF_CHECKED) : (MF_BYCOMMAND | MF_UNCHECKED));

        // hide
        // we use _fWantHide (not _fCanHide) to reflect what user asked
        // for, not what he got.  o.w. you can't tell what the state is
        // unless you actually get it.
        CheckMenuItem(hmenu, IDM_AB_AUTOHIDE,
            MF_BYCOMMAND | (_fWantHide ? MF_CHECKED : MF_UNCHECKED));

        CASSERT(PARENT_XTOPMOST == HWND_DESKTOP);   // for WM_ACTIVATE
        CASSERT(PARENT_BTMMOST() == HWND_DESKTOP);  // for WM_ACTIVATE
        if (_eMode & WBM_FLOATING)
        {
            // (for now) only desktop btm/topmost does autohide
            EnableMenuItem(hmenu, IDM_AB_AUTOHIDE, MF_BYCOMMAND | MF_GRAYED);
        }

#ifdef DEBUG
        // FEATURE temporary until we make browser tell us about activation
        CheckMenuItem(hmenu, IDM_AB_ACTIVATE,
            MF_BYCOMMAND | (_fActive ? MF_CHECKED : MF_UNCHECKED));
#endif

    }
    return hmenu;
}

HRESULT CDockingBar::_TrackPopupMenu(const POINT* ppt)
{
    HRESULT hres = S_OK;

    HMENU hmenu = _GetContextMenu();
    if (hmenu)
    {
        TrackPopupMenu(hmenu, /*TPM_LEFTALIGN|*/TPM_RIGHTBUTTON,
            ppt->x, ppt->y, 0, _hwnd, NULL);
        DestroyMenu(hmenu);
    }
    else
    {
        hres = E_OUTOFMEMORY;
    }

    return hres;
}

void CDockingBar::_HandleWindowPosChanging(LPWINDOWPOS pwp)
{
}

/***
 */
LRESULT CDockingBar::v_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lres = 0;
    POINT pt;
    DWORD pos;
    RECT rc;

    switch (uMsg) {
    case WM_CLOSE:
        _AppBarOnCommand(IDM_AB_CLOSE);   // _RemoveToolbar(0)
        break;

    case WM_DESTROY:
        if (_fAppRegistered)
            _AppBarRegister(FALSE);
        break;

    case APPBAR_CALLBACK:
        _AppBarCallback(hwnd, uMsg, wParam, lParam);
        return 0;

    case WM_CONTEXTMENU:
        if (_CheckForwardWinEvent(uMsg, wParam, lParam, &lres))
            break;
            
        if ((LPARAM)-1 == lParam)
        {
            GetClientRect(_hwnd, &rc);
            MapWindowRect(_hwnd, HWND_DESKTOP, &rc);
            pt.x = rc.left + (rc.right - rc.left) / 2;
            pt.y = rc.top + (rc.bottom - rc.top) / 2;
        }
        else
        {
            pt.x = GET_X_LPARAM(lParam);
            pt.y = GET_Y_LPARAM(lParam);
        }
        _TrackPopupMenu(&pt);
        break;

    case WM_ENTERSIZEMOVE:
        ASSERT(_fDragging == 0);
        _fDragging = 0;         // reset if busted
        _xyPending = XY_NIL;
#if XXX_CANCEL
        GetWindowRect(hwnd, &_rcCapture);      // to detect cancel
#endif
        break;

    case WM_SYSCHAR:
        if (wParam == TEXT(' '))
        {
            HMENU hmenu;

            hmenu = GetSystemMenu(hwnd, FALSE);
            if (hmenu) {
                EnableMenuItem(hmenu, SC_RESTORE, MFS_GRAYED | MF_BYCOMMAND);
                EnableMenuItem(hmenu, SC_MAXIMIZE, MFS_GRAYED | MF_BYCOMMAND);
                EnableMenuItem(hmenu, SC_MINIMIZE, MFS_GRAYED | MF_BYCOMMAND);
            }
        }
        goto DoDefault;

    case WM_SIZING:     // goto DoDefault? I doubt it.
    case WM_MOVING:
        {
            LPRECT prc = (RECT*)lParam;

            pos = GetMessagePos();
            if (_fDragging == 0)
            {
                // 1st time
                _DragEnter(uMsg, GET_X_LPARAM(pos), GET_Y_LPARAM(pos), prc);
                ASSERT(_fDragging != 0);
            }
            else
            {
                // 2nd..Nth time
                _DragTrack(uMsg, GET_X_LPARAM(pos), GET_Y_LPARAM(pos), prc, 0);
            }
        }
        return 1;

    case WM_MOVE:       // xLeft , yTop
    case WM_SIZE:       // xWidth, yHeight
        if (_fDragging)
        {
            RECT rcTmp;

            CopyRect(&rcTmp, &_rcPending);
            _DragTrack(uMsg, GET_X_LPARAM(_xyPending), GET_Y_LPARAM(_xyPending),
                &rcTmp, 1);
        }

        _OnSize();    // PERF: only needed for WM_SIZE? At worst this might cause a sligth flicker.
        break;
        
    case WM_EXITSIZEMOVE:
        _DragLeave(-1, -1, TRUE);
        ASSERT(_fDragging == 0);

        break;

    case WM_CHILDACTIVATE:
        if (_eMode == WBM_BFLOATING)
            SendMessage(_hwnd, WM_MDIACTIVATE, (WPARAM)_hwnd, 0);
        goto DoDefault;
        
    case WM_WINDOWPOSCHANGING:
        _HandleWindowPosChanging((LPWINDOWPOS)lParam);
        break;

    case WM_WINDOWPOSCHANGED:
Lfwdappbar:
        if (_fAppRegistered)
            _AppBarOnWM(uMsg, wParam, lParam);
        goto DoDefault;        // fwd on so we'll get WM_SIZE etc.

    case WM_TIMER:
        switch (wParam) {

        case IDT_AUTOHIDE:
            {
                ASSERT(_fWantHide && _fCanHide);

                GetCursorPos(&pt);
                GetWindowRect(hwnd, &rc);
                // add a bit of fudge so we don't hide when trying to grab the edge
                InflateRect(&rc, GetSystemMetrics(SM_CXEDGE) * 4,
                    GetSystemMetrics(SM_CYEDGE)*4);

                HWND hwndAct = GetActiveWindow();

                if (!PtInRect(&rc, pt) && hwndAct != hwnd &&
                  (hwndAct == NULL || ::GetWindowOwner(hwndAct) != hwnd))
                  {
                    // to hide, we need to be outside the inflated window,
                    // and we can't be active (for keyboard users).
                    // (heuristics stolen from tray.c)
                    // FEATURE: tray.c also checks TM_SYSMENUCOUNT == 0

                    _DoHide(AHO_KILLDO|AHO_MOVEDO);
                }
            }
            break;

        case IDT_AUTOUNHIDE:    // FEATURE: share code w/ IDT_AUTOHIDE
            ASSERT(_fWantHide && _fCanHide);

            if (_fHiding)
            {
                ASSERT(_fHiding == HIDE_AUTO);
                GetCursorPos(&pt);
                GetWindowRect(hwnd, &rc);
                if (PtInRect(&rc, pt))
                    _DoHide(AHO_KILLUN|AHO_MOVEUN|AHO_SETDO);
                else
        Lkillun:
                    _DoHide(AHO_KILLUN);
            }
            else
            {
                // if we mouse-over and then TAB very quickly, we can end
                // up getting a WM_ACT followed by a WM_TIMER (despite the
                // KillTimer inside OnAct).  if so we need to be careful
                // not to do an AHO_SETDO.  just to be safe we do an
                // AHO_KILLUN as well.
                TraceMsg(DM_HIDE, "cwb.WM_T: !_fHiding (race!) => AHO_KILLUN");
                goto Lkillun;
            }
            break;

        default:
            goto DoDefault;
        }

        break;

    case WM_NCLBUTTONDOWN:
    case WM_LBUTTONDOWN:
        goto DoDefault;


    case WM_ACTIVATE:
        _OnActivate(wParam, lParam);
        goto Lfwdappbar;

    case WM_GETMINMAXINFO:  // prevent it from getting too small
        // n.b. below stuff works for scheme 'win standard large'
        // but not for v. large edges.  not sure why, but we'll
        // have to fix it or the original bug will still manifest
        // on accessibility-enabled machines.

        // nt5:149535: resize/drag of v. small deskbar.
        // APPCOMPAT workaround USER hittest bug for v. small windows.
        // DefWndProc(WM_NCHITTEST) gives wrong result (HTLEFT) when
        // window gets too small.  so stop it from getting v. small.
        //
        // the below calc actually gives us slightly *more* than the
        // min size, but what the heck.  e.g. it gives 8+15+1=24,
        // whereas empirical tests give 20.  not sure why there's a
        // diff, but we'll use the bigger # to be safe.
        {
            RECT rcTmp = {100,100,100,100}; // arbitrary 0-sized rect
            LONG ws, wsx;
            HWND hwndTmp;

            _GetStyleForMode(_eMode, &ws, &wsx, &hwndTmp);
            AdjustWindowRectEx(&rcTmp, ws, FALSE, wsx);

            ((MINMAXINFO *)lParam)->ptMinTrackSize.x = RECTWIDTH(rcTmp)  + CXSMSIZE() + 1;
            ((MINMAXINFO *)lParam)->ptMinTrackSize.y = RECTHEIGHT(rcTmp) + CYSMSIZE() + 1;
            if (ISWBM_FLOAT(_eMode))
            {
                // nt5:169734 'close' button on v. small floating deskbar.
                // APPCOMPAT workaround USER 'close' button bug for v. small windows.
                // the button on a v. small TOOLWINDOW doesn't work.
                // empirically the below adjustment seems to work.
                ((MINMAXINFO *)lParam)->ptMinTrackSize.x += (CXSMSIZE() + 1) * 3 / 2;
                ((MINMAXINFO *)lParam)->ptMinTrackSize.y += (CYSMSIZE() + 1) * 3 / 2;
            }
            TraceMsg(DM_TRACE, "cwb.GMMI: x=%d", ((MINMAXINFO *)lParam)->ptMinTrackSize.x);
        }
        break;

    case WM_NCHITTEST:
        return _OnNCHitTest(wParam, lParam);

    case WM_WININICHANGE:
        // Active Desktop *broadcasts* a WM_WININICHANGE SPI_SETDESKWALLPAPER
        // message when starting up. If this message gets processed during
        // startup at just the right time, then the bands will notify their
        // preferred state, and we lose the persisted state.  Since the desktop
        // wallpaper changing is really of no interest to us, we filter it out here.
        //
        // REVIEW CDTURNER: Would we get a perf win by punting a larger class
        // of these wininichange messages? It seems like most won't affect
        // the contents of a deskbar...
        //
        if (SPI_SETDESKWALLPAPER == wParam)
            break;

        goto DoDefault;
        
    default:
DoDefault:
        return SUPERCLASS::v_WndProc(hwnd, uMsg, wParam, lParam);
    }

    return lres;
}

LRESULT CDockingBar::_OnNCHitTest(WPARAM wParam, LPARAM lParam)
{
    if (_fHiding)
        _DoHide(AHO_SETUN);

    // get 'pure' hittest...
    LRESULT lres = _CalcHitTest(wParam, lParam);

    // ... and perturb it based on where we're docked
    BOOL fSizing = FALSE;
    if (ISWBM_FLOAT(_eMode)) {
        // standard sizing/moving behavior
        return lres;
    }
    else {
        // opposing edge sizes; any other edge moves
        ASSERT(ISABE_DOCK(_uSide));
        switch (_uSide) {
        case ABE_LEFT:
            //  
            // Mirror the edges (since we are dealing with screen coord)
            // if the docked-window parent is mirrored. [samera]
            //
            if (IS_WINDOW_RTL_MIRRORED(GetParent(_hwnd)))
                fSizing = (lres==HTLEFT);
            else
                fSizing = (lres==HTRIGHT);
            break;
        case ABE_RIGHT:
            //  
            // Mirror the edges (since we are dealing with screen coord)
            // if the docked-window parent is mirrored.
            //
            if (IS_WINDOW_RTL_MIRRORED(GetParent(_hwnd)))
                fSizing = (lres==HTRIGHT);
            else
                fSizing = (lres==HTLEFT);
            break;
        case ABE_TOP:
            fSizing = (lres==HTBOTTOM);
            break;
        case ABE_BOTTOM:
            fSizing = (lres==HTTOP);
            break;

        default: 
            ASSERT(0); 
            break;
        }
    }

    if (!fSizing) {
        lres = HTCAPTION;
    }
    return lres;
}

//***   _OnActivate --
//
void CDockingBar::_OnActivate(WPARAM wParam, LPARAM lParam)
{
    TraceMsg(DM_HIDE, "cwb.WM_ACTIVATE wParam=%x", wParam);
    if (_fCanHide) {
        ASSERT(_fHiding != HIDE_MANUAL);
        if (LOWORD(wParam) != WA_INACTIVE) {
            // activate
            TraceMsg(DM_HIDE, "cdb._oa:  WM_ACT(act) _fHiding=%d", _fHiding);
            // turn off timers for perf
            // nash:40992: unhide if hidden (e.g. TABed to hidden)
            _DoHide(AHO_KILLDO|AHO_MOVEUN);
        }
        else {
            // deactivate
            _DoHide(AHO_SETDO);         // restore
        }
    }

    return;
}

/***
 */
CDockingBar::CDockingBar() : _eMode(WBM_NIL), _uSide(ABE_RIGHT)
{
    ASSERT(_fIdtUnHide == FALSE);
    ASSERT(_fIdtDoHide == FALSE);
    _ptIdtUnHide.x = _ptIdtUnHide.y = -1;

    // set up worst-case defaults.  we'll end up using them for:
    //     - some of them for Load(bag)
    //     - all  of them for InitNew()
    // note that we might call _InitPos4 again in SetSite.
    _InitPos4(TRUE);

    return;
}

//***   _Initialize -- 2nd-phase ctor
// NOTES
//  we need any IPS::Load settings and also a site before we can init
//  ourself, so most initialization waits until here.
void CDockingBar::_Initialize()
{
    ASSERT(!_fInitShowed);
    ASSERT(_fInitSited && _eInitLoaded);
    ASSERT(!CDB_INITED());

    _fInitShowed = TRUE;

    // warning: delicate phase-ordering here...
    UINT eModeNew = _eMode;
    _eMode = WBM_NIL;
    UINT uSideNew = _uSide;
    _uSide = ABE_NIL;
    HMONITOR hMonNew = _hMon;
    _hMon = NULL;
    // 48463: beta reports fault on boot when we have deskbar+taskbar on
    // same edge (non-merged).  i'm guessing (no proof) that shdocvw isn't
    // init'ed enough early on during boot to handle doing a MergeBS, or
    // alternately that there's a race btwn the tray and desktop threads.
    //
    // plus in any case we shouldn't do the merge just because the guy did
    // a logoff/logon!
    _SetModeSide(eModeNew, uSideNew, hMonNew, /*fNoMerge*/_eInitLoaded == IPS_LOAD);

    _NotifyModeChange(0);

    // if we have a bar on the right and we drag a band from it to
    // the top, we end up getting a sequence:
    //      create deskbar; AddBand; SetSite; _Initialize
    // the AddBand of the (1st) band tries to do an autosize but
    // there's no site yet, so nothing happens.
    //
    // so we need to force it here.
    _AdjustToChildSize();

    if (_fWantHide) {
        _fWantHide = FALSE;
        _AppBarOnCommand(IDM_AB_AUTOHIDE);
    }

    ASSERT(CDB_INITED());

    return;
}

/***
 */
CDockingBar::~CDockingBar()
{
    ASSERT(!_fAppRegistered);   // make sure _ChangeTopMost(WBM_NIL) was called

    // make sure SetSite(NULL); was called
    ASSERT(!_ptbSite);
    return;
}


void CDockingBar::_GetChildPos(LPRECT prc)
{
    GetClientRect(_hwnd, prc);
}

//***   _OnSize -- compute size for OC, leaving room for toolbar (caption?)
//
void CDockingBar::_OnSize(void)
{
    RECT rc;

    if (!_hwndChild || !_eInitLoaded)
        return;

    ASSERT(IsWindow(_hwndChild));

    // don't resize on a hide (it's temporary and we don't want things
    // to jerk around or worse still do a destructive reformat)
    // APPCOMPAT: should suppress resizing here in theater mode autohide
    // too (see theater.cpp)
    if (_fHiding)
        return;

    _GetChildPos(&rc);
    // (used to do ISWBM_EDGELESS 'fake edge' adjustments here, someone
    // nuked them, but should be o.k. now that visuals are frozen *provided*
    // we don't go back to edgeless)

    SetWindowPos(_hwndChild, 0,
            rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc),
            SWP_NOACTIVATE|SWP_NOZORDER);
    //ASSERT(DbCheckWindow(_hwndChild, &rc, xxx));
}

//***   _CalcHitTest --
// NOTES
//      really only has to return an int (win16?)
LRESULT CDockingBar::_CalcHitTest(WPARAM wParam, LPARAM lParam)
{
    LRESULT lRet;

    if (!(ISWBM_BOTTOM(_eMode) && ISWBM_EDGELESS(_eMode))) {
        // For non-btmmost, we can ask USER to perform the default
        // hit testing.
        lRet = DefWindowProcWrap(_hwnd, WM_NCHITTEST, wParam, lParam);
    } else {
        // For btmmost, we need to do it.
        // (possibly dead code if bottom is never edgeless)
        // (if so, compiler should optimize it out)

        //TraceMsg(DM_WARNING, "cdb.ro: edgeless!");

        RECT rc;
        GetWindowRect(_hwnd, &rc);
        UINT x = GET_X_LPARAM(lParam);
        UINT y = GET_Y_LPARAM(lParam);
        // actually SM_C?SIZEFRAME is too big, but we get away w/ it
        // since we've been initiated by a WM_NCHITTEST so we know
        // we're on *some* edge
        UINT cx = GetSystemMetrics(SM_CXSIZEFRAME);
        UINT cy = GetSystemMetrics(SM_CYSIZEFRAME);
        if (_eMode == WBM_BBOTTOMMOST)
            cx *= 2;

        lRet = HTCAPTION;
        if (x > rc.right-cx) {
            lRet = HTRIGHT;
        } else if (x < rc.left+cx) {
            lRet = HTLEFT;
        } else if (y < rc.top+cy) {
            lRet = HTTOP;
        } else if (y > rc.bottom-cy) {
            lRet = HTBOTTOM;
        }
    }

    return lRet;
}

//***
//
void CDockingBar::_DragEnter(UINT uMsg, int x, int y, RECT* rcFeed)
{
    ASSERT(_fDragging == 0);

    if ((!(_eMode & WBM_FLOATING)) && uMsg == WM_MOVING)
    {
        // APPCOMPAT workaround USER non-full-drag drag rect bug
        // by forcing rcFeed back to exact current location (rather than
        // leaving at initial offset that USER gave us).
        //
        // w/o this code a drag from right to top in non-full-drag mode
        // will leave drag-rect droppings at the top of the original
        //
        // APPCOMPAT but, this seems to make things *worse* if !ISWBM_DESKTOP(),
        // so we don't do it in that case...  (sigh).
        _MoveSizeHelper(_eMode, _uSide, _hMon, NULL, rcFeed, FALSE, FALSE);
    }

    _eModePending = _eMode;
    _uSidePending = _uSide;
    _xyPending = MAKELPARAM(x, y);
    _hMonPending = _hMon;
    ASSERT(rcFeed != 0);
    if (rcFeed != 0)
        CopyRect(&_rcPending, rcFeed);

#if XXX_CANCEL
    RECT rcTmp;

    GetWindowRect(_hwnd, &rcTmp);
    TraceMsg(DM_DRAG2,
        "cwb.de: rcTmp=(%d,%d,%d,%d) (%dx%d) _rcCapture=(%d,%d,%d,%d) (%dx%d)",
        rcTmp.left, rcTmp.top, rcTmp.right, rcTmp.bottom,
        RECTWIDTH(rcTmp), RECTHEIGHT(rcTmp),
        _rcCapture.left, _rcCapture.top, _rcCapture.right, _rcCapture.bottom,
        RECTWIDTH(_rcCapture), RECTHEIGHT(_rcCapture));
#endif

    switch (uMsg) {
    case WM_MOVING: _fDragging = DRAG_MOVE; break;
    case WM_SIZING: _fDragging = DRAG_SIZE; break;

    default: ASSERT(0); break;
    }

    if (_fDragging == DRAG_MOVE) {
        // turn off size negotiation to prevent horz/vert pblms.
        //
        // e.g. when we drag a floating guy to horz/vert, there's
        // a period of time during which we have a horz/vert size,
        // but still think we're floating, which screws up size
        // negotiation royally.
        _ExecDrag(DRAG_MOVE);
    }

    return;
}

//***
//
void CDockingBar::_DragTrack(UINT uMsg, int x, int y, RECT* rcFeed, int eState)
{
#if DM_API
    TraceMsg(DM_DRAG2,
        "cwb.dt: API s=%d xy=(%d,%d) rc=(%d,%d,%d,%d) (%dx%d)",
        eState, x, y,
        _PM(rcFeed,left), _PM(rcFeed,right), _PM(rcFeed,bottom), _PM(rcFeed,right),
        _PX(rcFeed,RECTWIDTH(*rcFeed)), _PX(rcFeed,RECTHEIGHT(*rcFeed)));
#endif

    ASSERT(_fDragging != 0);

    switch (eState) {
    case 0:     // WM_MOVING
        {
            BOOL fImmediate = ((!_fDesktop) && uMsg == WM_SIZING) ? TRUE:FALSE;

            // remember for eventual commit
            _xyPending = MAKELPARAM(x, y);
            ASSERT(rcFeed != NULL);
            CopyRect(&_rcPending, rcFeed);

            // snap and give feedback
            _TrackSliding(x, y, rcFeed, fImmediate, fImmediate);

            break;
        }
    case 1:     // WM_MOVE
        TraceMsg(DM_DRAG2,
            "cwb.dt: %s _xyPend=(%d,%d) xy=(%d,%d)",
            (_xyPending != MAKELPARAM(x, y)) ? "noop/cancel" : "commit",
            GET_X_LPARAM(_xyPending), GET_Y_LPARAM(_xyPending), x, y);

        break;

    default: ASSERT(0); break;
    }

    return;
}

//***
//
void CDockingBar::_DragLeave(int x, int y, BOOL fCommit)
{
#if DM_API
    TraceMsg(DM_DRAG,
        "cwb.dl: API xy=(%d,%d) fCommit=%d",
        x, y, fCommit);
#endif

    if (_fDragging == 0) {
        // when we're inside a browser and you move the browser window
        // we get WM_ENTERSIZEMOVE/ WM_EXITSIZEMOVE but never any
        // WM_MOVING/WM_MOVE/WM_SIZING/WM_SIZE
        return;
    }

    switch (_fDragging) {
    case DRAG_MOVE:
    case DRAG_SIZE:
        break;
    default: ASSERT(0); break;
    }

#if XXX_CANCEL
    RECT rcTmp;

    GetWindowRect(_hwnd, &rcTmp);
    TraceMsg(DM_DRAG2,
        "cwb.dl: rcTmp=(%d,%d,%d,%d) (%dx%d) _rcCapture=(%d,%d,%d,%d) (%dx%d)",
        rcTmp.left, rcTmp.top, rcTmp.right, rcTmp.bottom,
        RECTWIDTH(rcTmp), RECTHEIGHT(rcTmp),
        _rcCapture.left, _rcCapture.top, _rcCapture.right, _rcCapture.bottom,
        RECTWIDTH(_rcCapture), RECTHEIGHT(_rcCapture));
    TraceMsg(DM_DRAG2, "cwb.dl: %s",
        EqualRect(&rcTmp, &_rcCapture) ? "noop/cancel" : "commit");
#endif

    BOOL fCancel = FALSE;       // FEATURE: todo: cancel NYI

    if (!fCancel) {
        if (_fDragging == DRAG_MOVE) {
            // nt5:187720 do this *before* the final move.
            // o.w. addr band ends up w/ 80-high default rather than
            // snapped to correct/negotiated size.
            //
            // why are we able to turn this on here when in general it
            // had to be off during the drag?  well, the preview of the
            // drag went thru MoveSizeHelper which did a NotifyModeChange
            // which told our client what its orientation really is.  so
            // by now things should be in sync.

            // size negotiation had been turned off (to prevent horz/vert
            // pblms).  turn it on before the final move so that we'll
            // recalc correctly.
            _ExecDrag(0);
        }

        // (we're done w/ _rcPending so o.k. to pass it in and trash it)
        // fMove==TRUE even though USER has already done the move for us,
        // since it's only done the move not the resize (?).  if we use
        // fMove==FALSE we end up in the new location but w/ the old size,
        // despite the fact that rcFeed has been updated along the way.
        // this is because USER sets SWP_NOSIZE when it does the move.
        _TrackSliding(GET_X_LPARAM(_xyPending), GET_Y_LPARAM(_xyPending),
            &_rcPending, TRUE, TRUE);

        // if we got a preferred child sizewhild dragging, set ourselves to that now.
        // sizing up   (cx > min), _szChild.cx == 0 and call a noop.
        // sizing down (cx < min), _szChild.cx != 0 and call does something.
        //ASSERT(_szChild.cx == 0);   // 0 => _AdjustToChildSize is nop
        _AdjustToChildSize();
    }
    else {
        _MoveSizeHelper(_eMode, _uSide, _hMon, NULL, NULL, TRUE, FALSE);   // FEATURE: fMove?
    }

    _fDragging = 0;
    
    return;
}

#ifdef DEBUG
int g_dbNoExecDrag = 0;     // to play w/ ExecDrag w/o recompiling
#endif

void DBC_ExecDrag(IUnknown *pDBC, int eDragging)
{
    VARIANTARG vaIn = {0};      // VariantInit

    ASSERT(eDragging == DRAG_MOVE || eDragging == 0);

#ifdef DEBUG
    if (g_dbNoExecDrag)
        return;
#endif

    vaIn.vt = VT_I4;
    vaIn.lVal = eDragging;      // n.b. currently only 0/1 is supported 
    IUnknown_Exec(pDBC, &CGID_DeskBarClient, DBCID_ONDRAG, OLECMDEXECOPT_DONTPROMPTUSER, &vaIn, NULL);
    // VariantClear

    return;
}

void CDockingBar::_ExecDrag(int eDragging)
{
    DBC_ExecDrag(_pDBC, eDragging);
    return;
}

//***   _Recalc -- force recalc using current settings
//
void CDockingBar::_Recalc(void)
{
    _MoveSizeHelper(_eMode, _uSide, _hMon, NULL, NULL, TRUE, TRUE);
    return;
}

//***   _MoveSizeHelper -- shared code for menu and dragging forms of move/size
//
void CDockingBar::_MoveSizeHelper(UINT eModeNew, UINT eSideNew, HMONITOR hMonNew,
    POINT* ptTrans, RECT* rcFeed, BOOL fCommit, BOOL fMove)
{
    UINT eModeOld, eSideOld;

    RECT rcNew;

    // only desktop guys can go to TOPMOST
    ASSERT(eModeNew != WBM_TOPMOST || ISWBM_DESKTOP());

    eModeOld = _eMode;
    eSideOld = _uSide;

    ASSERT(CHKWBM_CHANGE(eModeNew, _eMode));
    _eModePending = eModeNew;   // for drag feedback, before commit
    _uSidePending = eSideNew;
    _hMonPending = hMonNew;

    if (fCommit)
    {
        // we need to be careful when we call _ChangeHide or we'll recurse
        BOOL fChangeHide = (_fWantHide &&
            (eSideNew != _uSide || eModeNew != _eMode || hMonNew != _hMon));

        if (fChangeHide)
            _DoHide(AHO_KILLDO|AHO_UNREG);

        _SetModeSide(eModeNew, eSideNew, hMonNew, FALSE);

        if (fChangeHide)
        {
            // don't do AHO_SETDO now, wait for WM_ACTIVATE(deactivate)
            _DoHide(AHO_REG);
        }
    }

    // negotiate (and possibly commit to negotiation)
    _ProtoRect(&rcNew, eModeNew, eSideNew, hMonNew, ptTrans);
    _NegotiateRect(eModeNew, eSideNew, hMonNew, &rcNew, fCommit);

    // commit
    if (fCommit)
        _SetVRect(&rcNew);

    // feedback
    if (rcFeed != 0)
    {
        CopyRect(rcFeed, &rcNew);
    }
    
    if (fMove)
    {
        // If we're in theater mode, out parent manages our width and
        // horizontal position, unless we're being forced to a new
        // size by szChild.
        if (_fTheater && !_fDragging)
        {
            RECT rcCur;
            GetWindowRect(_hwnd, &rcCur);
            rcNew.left = rcCur.left;
            rcNew.right = rcCur.right;
        }

        // aka ScreenToClient
        MapWindowPoints(HWND_DESKTOP, GetParent(_hwnd), (POINT*) &rcNew, 2);
        
        if (_fCanHide && eModeNew == eModeOld && eSideNew == eSideOld)
        {
            // if we're [un]hiding to the same state, we can do SlideWindow
            ASSERT(ISWBM_HIDEABLE(eModeNew));
            DAD_ShowDragImage(FALSE);   // unlock the drag sink if we are dragging.
            SlideWindow(_hwnd, &rcNew, _hMon, !_fHiding);
            DAD_ShowDragImage(TRUE);    // restore the lock state.
        }
        else
        {
            MoveWindow(_hwnd, rcNew.left, rcNew.top,
                       RECTWIDTH(rcNew), RECTHEIGHT(rcNew), TRUE);
        }
    }

    // WARNING: rcNew is no longer in consistent coords! (ScreenToClient)

    // notify the child of changes
    _NotifyModeChange(0);
}

void CDockingBar::_NotifyModeChange(DWORD dwMode)
{
    UINT eMode, uSide;

    eMode = ((_fDragging == DRAG_MOVE) ? _eModePending : _eMode);
    uSide = ((_fDragging == DRAG_MOVE) ? _uSidePending : _uSide);
    //hMon = ((_fDragging == DRAG_MOVE) ? _hMonPending : _hMon);

    if (ISWBM_FLOAT(eMode))
        dwMode |= DBIF_VIEWMODE_FLOATING;
    else if (!ABE_HORIZ(uSide))
        dwMode |= DBIF_VIEWMODE_VERTICAL;

    SUPERCLASS::_NotifyModeChange(dwMode);
    
}

void CDockingBar::_TrackSliding(int x, int y, RECT* rcFeed,
    BOOL fCommit, BOOL fMove)
{
    TraceMsg(DM_DRAG2,
        "cwb.ts: _TrackSliding(x=%d, y=%d, rcFeed=(%d,%d,%d,%d)(%dx%d), fCommit=%d, fMove=%d)",
        x, y,
        _PM(rcFeed,left), _PM(rcFeed,top), _PM(rcFeed,right), _PM(rcFeed,bottom),
        _PX(rcFeed,RECTWIDTH(*rcFeed)), _PX(rcFeed,RECTHEIGHT(*rcFeed)),
        fCommit, fMove);

    POINT pt = { x, y };
    UINT eModeNew, uSideNew;
    HMONITOR hMonNew;
    if (_fDragging == DRAG_MOVE) {
        // moving...

        if (fCommit) {
            // use last feedback position.
            // o.w. (if we recompute) we end up in the wrong place since
            // WM_MOVE gives us the (left,top), which often is in another
            // docking zone.
            ASSERT(x == GET_X_LPARAM(_xyPending) && y == GET_Y_LPARAM(_xyPending));
            //eModeNew = _eModePending;
            //uSideNew = _uSidePending;
        }

        //
        // figure out snap position,
        // and do a few special-case hacks to fix it up if necessary
        //
        uSideNew = _CalcDragPlace(pt, &hMonNew);
        if (uSideNew == ABE_XFLOATING) {
            // dock->float or float->float
            eModeNew = _eMode | WBM_FLOATING;
            uSideNew = _uSide;          // FEATURE: _uSidePending? This seems to work correctly as is.
        }
        else {
            // float->dock or dock->dock
            eModeNew = _eMode & ~WBM_FLOATING;
        }

        TraceMsg(DM_DRAG2,
            "cwb.ts: (m,s) _x=(%d,%d) _xPend=(%d,%d) xNew=(%d,%d)",
            _eMode, _uSide, _eModePending, _uSidePending, eModeNew, uSideNew);

        // 970725: we now allow bottom->float (for the desktop, not browser)
        if (ISWBM_FLOAT(eModeNew) && ISWBM_BOTTOM(_eMode) && !ISWBM_DESKTOP()) {
            // special case: don't allow switch from BTMMOST to FLOATING
            ASSERT(CHKWBM_CHANGE(eModeNew, _eMode));
            eModeNew = _eModePending;   // the dead zone...
            uSideNew = _uSidePending;   // QUESTION: init case?
            hMonNew = _hMonPending;
            TraceMsg(DM_DRAG2,
                "cwb.ts: (m,s) btm->flt override     xNew=(%d,%d)",
                eModeNew, uSideNew);
            ASSERT(!ISWBM_FLOAT(eModeNew));
        }

        //
        // smooth things out so we don't jump around
        //
        _SmoothDragPlace(eModeNew, uSideNew, hMonNew, &pt, rcFeed);

        //
        // now do the move
        //
        // | with _eMode & WBMF_BROWSER because dragging around doesn't change the
        // browser owned bit
        _MoveSizeHelper(eModeNew | (_eMode & WBMF_BROWSER), uSideNew, hMonNew, 
            ISWBM_FLOAT(eModeNew) ? &pt : NULL, rcFeed, fCommit, fMove);
    }
    else {
        ASSERT(_fDragging == DRAG_SIZE);

        // truncate to max size if necessary
        _SmoothDragPlace(_eMode, _uSide, _hMon, NULL, rcFeed);

        if (!fCommit) {
            // USER does everything for us
            return;
        }
        ASSERT(MAKELPARAM(x, y) != XY_NIL);

        // APPCOMPAT: we're gonna commit so just blast it in here...
        RECT rcNew;

        GetWindowRect(_hwnd, &rcNew);   // PERF: already set?
        _SetVRect(&rcNew);
        _MoveSizeHelper(_eMode, _uSide, _hMon,
            NULL, NULL,                 // FEATURE: &rcNew?
            fCommit, fMove);
    }

    return;
}

/***    _CalcDragPlace -- compute where drag will end up
 * NOTES
 *      FEATURE: prelim version
 */
UINT CDockingBar::_CalcDragPlace(POINT& pt, HMONITOR * phMon)
{
    TraceMsg(DM_DRAG2,
        "cwb.cdp: _CalcDragPlace(pt=(%d,%d))",
        pt.x, pt.y);

    SIZE screen, error;
    UINT uHorzEdge, uVertEdge, uPlace;
    RECT rcDisplay = {0};  // _GetBorderRect doesn't always set rect.

    // Get the correct hMonitor.
    ASSERT(phMon);
    *phMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
    // FEATURE: todo: make hwndSite and rcDisplay args, then this can be
    // a generic helper func
    _GetBorderRect(*phMon, &rcDisplay);

    // if we're outside our 'parent' (browser or desktop), we float.
    // this really only applies to the browser case, since we'll never
    // be outside the desktop (what about multi-monitor?).
    if (!PtInRect(&rcDisplay, pt) || !_ptbSite) {
        TraceMsg(DM_DRAG2,
            "cwb.cdp: pt=(%d,%d) uSideNew=%u",
            pt.x, pt.y, ABE_XFLOATING);
        return ABE_XFLOATING;
    }

    // if we're not w/in min threshhold from edge, we float
    {
        RECT rcFloat;   // FEATURE can just use rcDisplay
        int cx = CXFLOAT();
        int cy = CYFLOAT();

        CopyRect(&rcFloat, &rcDisplay);
        InflateRect(&rcFloat, -cx, -cy);

        if (PtInRect(&rcFloat, pt)) {
            TraceMsg(DM_DRAG2,
                "cwb.cdp: pt=(%d,%d) uSideNew=%u",
                pt.x, pt.y, ABE_XFLOATING);
            return ABE_XFLOATING;
        }
    }

    //
    // re-origin at zero to make calculations simpler
    //
    screen.cx =  RECTWIDTH(rcDisplay);
    screen.cy = RECTHEIGHT(rcDisplay);
    pt.x -= rcDisplay.left;
    pt.y -= rcDisplay.top;

    //
    // are we closer to the left or right side of this display?
    //
    if (pt.x < (screen.cx / 2)) {
        uVertEdge = ABE_LEFT;
        error.cx = pt.x;
    }
    else {
        uVertEdge = ABE_RIGHT;
        error.cx = screen.cx - pt.x;
    }

    //
    // are we closer to the top or bottom side of this display?
    //
    if (pt.y < (screen.cy / 2)) {
        uHorzEdge = ABE_TOP;
        error.cy = pt.y;
    }
    else {
        uHorzEdge = ABE_BOTTOM;
        error.cy = screen.cy - pt.y;
    }

    //
    // closer to a horizontal or vertical edge?
    //
    uPlace = ((error.cy * screen.cx) > (error.cx * screen.cy))?
        uVertEdge : uHorzEdge;

    TraceMsg(DM_DRAG2,
        "cwb.cdp: pt=(%d,%d) uSideNew=%u",
        pt.x, pt.y, uPlace);

    return uPlace;
}

//***   _SmoothDragPlace -- do some magic to smooth out dragging
// ENTRY/EXIT
//      eModeNew        where we're snapping to
//      eSideNew        ...
//      [_eModePending] where we're snapping from
//      [_eSidePending] ...
//      pt              INOUT cursor position
//      rcFeed          USER's original drag feedback rect
// NOTES
//      this is the place to put excel-like heuristics.  e.g. when coming
//      back off the right side we could put the cursor at the top right of
//      the floating rect (rather than the top left) to allow us to float
//      as close as possible to the other side w/o docking.  hmm, but how
//      would we tell USER where to put the cursor...
//
void CDockingBar::_SmoothDragPlace(UINT eModeNew, UINT eSideNew, HMONITOR hMonNew,
    INOUT POINT* pt, RECT* rcFeed)
{
    if (_fDragging == DRAG_MOVE) {
        if (ISWBM_FLOAT(eModeNew) && ISWBM_FLOAT(_eModePending) && rcFeed != 0 && pt) {
            // use the feedback rect from USER to keep things smooth.
            // o.w. if we use the cursor position we'll jump at the
            // beginning (to move the left-top corner to the starting
            // cursor position).
            pt->x = rcFeed->left;
            pt->y = rcFeed->top;
        }
    }
    else {
        ASSERT(_fDragging == DRAG_SIZE);
        ASSERT(eModeNew == _eMode && eSideNew == _uSide && hMonNew == _hMon);
        if (!ISWBM_FLOAT(_eMode)) {
            // truncate to max size (1/2 of screen) if necessary

            int iWH;
            RECT rcScreen;

            // we'd like to use 1/2 of browser, not 1/2 of screen.  however
            // this causes pblms if you maximize, grow to 1/2, and restore.
            // then the 1st time you resize the bar it 'jumps' down to 1/2
            // of the *current* size from 1/2 of the old size.  kind of a
            // hack, sigh...
            //
            // also note that there's still a bug here: if you size down
            // the browser gradually, we don't go thru this logic, so you
            // end up w/ a bar width > browser width so you the right edge
            // is clipped and there's no way to size it down.  probably
            // when the browser resize is done we should re-smooth the bar.
            //_GetBorderRect(_hMon, &rcScreen);
            GetMonitorRect(_hMon, &rcScreen);   // aka GetSystemMetrics(SM_CXSCREEN)
            iWH = RECTGETWH(_uSide, &rcScreen);
            iWH /= 2;
            if (RECTGETWH(_uSide, rcFeed) > iWH) {
                TraceMsg(DM_TRACE, "cwb.sdp: truncate iWH'=%d", iWH);
                RectXform(rcFeed, RX_OPPOSE, rcFeed, NULL, iWH, _uSide, NULL);
            }
        }
    }

    return;
}

/***
 */
LRESULT CDockingBar::_OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lres = 0;

    if (_CheckForwardWinEvent(uMsg, wParam, lParam, &lres))
        return lres;
    
    if ((Command_GetID(wParam) <= IDM_AB_LAST) &&
        (Command_GetID(wParam) >= IDM_AB_FIRST)) {
        _AppBarOnCommand(Command_GetID(wParam));
    }

    return lres;
}

/***    CDockingBar::_AppBarRegister -- register/unregister AppBar
 * DESCRIPTION
 *      updates _fAppRegistered
 *      does nothings if it's already regisrered or unregistered
 */
void CDockingBar::_AppBarRegister(BOOL fRegister)
{
    APPBARDATA abd;

    abd.cbSize = sizeof(APPBARDATA);
    abd.hWnd = _hwnd;

    if (fRegister && !_fAppRegistered) {
        abd.uCallbackMessage = APPBAR_CALLBACK;
        TraceMsg(DM_APPBAR, "cwb.abr: call ABM_NEW");
        UINT_PTR bT = SHAppBarMessage(ABM_NEW, &abd);
        ASSERT(bT);
        if (bT) {
            _fAppRegistered = TRUE;

            // fake a callback to set initial state
            // #if XXX_TASKMAN
            TraceMsg(DM_APPBAR, "cwb.abr: fake ABN_STATECHANGE");
            _AppBarCallback(_hwnd, APPBAR_CALLBACK, ABN_STATECHANGE, 0);
            // #endif
        }
    }
    else if (!fRegister && _fAppRegistered) {
        TraceMsg(DM_APPBAR, "cwb.abr: call ABM_REMOVE");
        // n.b. sensitive phase ordering, must set flag before send message
        // since the message causes a bunch of callbacks
        _fAppRegistered = FALSE;
        SHAppBarMessage(ABM_REMOVE, &abd);
    }
}

//***   _SetVRect -- set our 'virtual rect' to reflect window state
//
void CDockingBar::_SetVRect(RECT* rcNew)
{
    UINT eModeNew, uSideNew;

    //ASSERT(_fDragging == 0);  // o.w. we should look at _xxxPending

    eModeNew = _eMode;
    uSideNew = _uSide;


    if (_fHiding && ISWBM_HIDEABLE(eModeNew)) {
        TraceMsg(DM_HIDE, "cwb.svr: _fHiding => suppress rcNew=(%d,%d,%d,%d)",
            rcNew->left, rcNew->top, rcNew->right, rcNew->bottom);
        return;
    }


    if (ISWBM_FLOAT(eModeNew)) {
        CopyRect(&_rcFloat, rcNew);
    }
    else {
        _adEdge[uSideNew] = ABE_HORIZ(uSideNew) ? RECTHEIGHT(*rcNew) : RECTWIDTH(*rcNew);
    }
    return;
}

//***   _ChangeTopMost -- switch back and forth btwn TopMost and BottomMost
// ENTRY/EXIT
//      eModeNew        new mode we're switching to
//
void CDockingBar::_ChangeTopMost(UINT eModeNew)
{
    BOOL fShouldRegister = (eModeNew & WBM_TOPMOST) && !(eModeNew & WBM_FLOATING);
    
    // here's what's legal...
//              to...................
// from         btm     top     float
// ----         ---     ---     -----
// btm(desk)    -       top+    y(1)    (1) force to top
// top          top-    -       'undock'
// float        y(2)    'dock'  -       (2) force to right
// btm(app)     -       x(3)    y(4)    (3) foster child (4) 'owned' window


#if 0
    // (1,4) going from BTMMOST to FLOATING is illegal (and NYI) unless desktop
    ASSERT(eModeNew != WBM_FLOATING || _eMode != WBM_BOTTOMMOST || ISWBM_DESKTOP());
#endif

    // (3) only desktop guys can go to TOPMOST
    ASSERT(eModeNew != WBM_TOPMOST || ISWBM_DESKTOP());

    // _uSide should always be laying around (even if floating)
    ASSERT(_eMode == WBM_NIL || ISABE_DOCK(_uSide));

    // note the ordering here, make sure window bits are right
    // before doing resume new or else new will have unexpected state
    _ChangeWindowStateAndParent(eModeNew);
    _eMode = eModeNew;
    _ChangeZorder();

    // resume new
    switch (_eMode) {
    case WBM_NIL:
        // dummy state for termination
        return;

    case WBM_BOTTOMMOST:
        _ResetZorder();
#if ! XXX_BROWSEROWNED
        // fall through
    case WBM_BBOTTOMMOST:
#endif
        break;
    }
    
    _AppBarRegister(fShouldRegister);
}

//***   _ChangeZorder -- set z-order appropriately
// NOTES
//  currently doesn't account for 'raised' mode (i.e. caller must call
//  _ChangeZorder before _ResetZorder)
void CDockingBar::_ChangeZorder()
{
    BOOL fWantTopmost = BOOLIFY(WBM_IS_TOPMOST());
    BOOL fIsTopmost = BOOLIFY(GetWindowLong(_hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST);
    if (fWantTopmost != fIsTopmost)
        SetWindowPos(_hwnd, fWantTopmost ? HWND_TOPMOST : HWND_NOTOPMOST,
                     0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    return;
}

//***    _ResetZorder -- toggle DockingBars between 'normal' and 'raised' mode
// DESCRIPTION
//  queries desktop state and does appropriate '_OnRaise'
//
void CDockingBar::_ResetZorder()
{
    HRESULT hr;
    VARIANTARG vaIn = {0};      // VariantInit
    VARIANTARG vaOut = {0};     // VariantInit

    vaIn.vt = VT_I4;
    vaIn.lVal = DTRF_QUERY;
    hr = IUnknown_Exec(_ptbSite, &CGID_ShellDocView, SHDVID_RAISE, OLECMDEXECOPT_DONTPROMPTUSER,
        &vaIn, &vaOut);
    if (SUCCEEDED(hr) && vaOut.vt == VT_I4)
        _OnRaise(vaOut.lVal);
    // VariantClear
    return;
}

//***   _OnRaise -- handle desktop 'raise' command
// DESCRIPTION
//  changes DockingBar z-order depending on desktop raise state:
//      desktop     DockingBar
//      raised      force on top (so visible)
//      restored    return to normal
// NOTES
//  FEATURE: should we handle WBM_FLOATING too?
//  FEATURE: should add ZORD_xxx to deskbar.h and handle non-WBM_BOTTOMMOST
void CDockingBar::_OnRaise(UINT flags)
{
    HWND hwndZorder;

    if (_eMode != WBM_BOTTOMMOST)
        return;

    switch (flags) {
    case DTRF_RAISE:
        hwndZorder = HWND_TOPMOST;
        break;

    case DTRF_LOWER:
        hwndZorder = HWND_NOTOPMOST;
        break;
    
    default:
        ASSERT(0);
        return;
    }

    SetWindowPos(_hwnd, hwndZorder, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);

    return;
}

#if XXX_BTMFLOAT && 0 // see 'NOTES'
//***   _MayReWindow -- change/restore window state for dragging
// DESCRIPTION
//      USER won't let us drag outside of the browser unless we reparent
//      ourselves.
// NOTES
//      need to call this *before* we enter USER's move/size loop.
//      i.e. on LBUTTONDOWN and on EXITSIZEMOVE.  this gets a bit
//      tricky due to CANCEL etc., since the LBUTTONUP may come thru
//      either before or after we're done.
//
void CDockingBar::_MayReWindow(BOOL fToFloat)
{

    if (ISWBM_DESKTOP() || _eMode != WBM_BOTTOMMOST)
        return;

    // do style bits 1st or re-parenting breaks
    SHSetWindowBits(_hwnd, GWL_STYLE, WS_CHILD | WS_POPUP, fToFloat ? WS_POPUP | WS_CHILD);

    if (!fToFloat) {
        // float->btm

        // nuke owner
        SHSetParentHwnd(_hwnd, NULL);

        // parent
        SetParent(_hwnd, _hwndSite);
    }


    if (fToFloat) {
        // btm->float, set owner

        // parent
        SetParent(_hwnd, PARENT_FLOATING);

        // set owner
        ASSERT(_hwndSite != NULL);
        SHSetParentHwnd(_hwnd, _hwndSite);
    }
}
#endif


void CDockingBar::_GetStyleForMode(UINT eMode, LONG* plStyle, LONG* plExStyle, HWND* phwndParent)
{
    switch (eMode) {
    case WBM_NIL:
        *plStyle = WS_NIL;
        *plExStyle= WS_EX_NIL;
        *phwndParent = PARENT_NIL;
        break;

    case WBM_BBOTTOMMOST:
        *plStyle = WS_BBTMMOST;
        *plExStyle= WS_EX_BBTMMOST;
        *phwndParent = PARENT_BBTMMOST();
        break;

    case WBM_BOTTOMMOST:
        *plStyle = WS_BTMMOST;
        *plExStyle= WS_EX_BTMMOST;
        *phwndParent = PARENT_BTMMOST();
        break;

    case WBM_BFLOATING:
        // FEATURE: todo: FLOATING NYI
        *plStyle = WS_BFLOATING;
        *plExStyle = WS_EX_BFLOATING;
        *phwndParent = _hwndSite;
        break;

    case (WBM_FLOATING | WBM_TOPMOST):
    case WBM_FLOATING:
        // FEATURE: todo: FLOATING NYI
        *plStyle = WS_FLOATING;
        *plExStyle = WS_EX_FLOATING;
        *phwndParent = PARENT_FLOATING;
        break;

    case WBM_TOPMOST:
        *plStyle = WS_XTOPMOST;
        *plExStyle= WS_EX_XTOPMOST;
        *phwndParent = PARENT_XTOPMOST;
        break;
    }
#ifdef DEBUG // {
    if (_eMode == eMode) {
        // style, exstyle
        ASSERT(BITS_SET(GetWindowLong(_hwnd, GWL_STYLE), *plStyle));
        ASSERT(BITS_SET(GetWindowLong(_hwnd, GWL_EXSTYLE), *plExStyle & ~WS_EX_TOPMOST));

        // id
        ASSERT(GetWindowLong(_hwnd, GWL_ID) == 0);

        // parent 
        ASSERT(GetParent(_hwnd) == *phwndParent ||
               (ISWBM_OWNED(_eMode) && GetParent(_hwnd)==_hwndSite));
    }
#endif // }
}

//***   _ChangeWindowStateAndParent --
// NOTES
//      todo: make table-driven (ws1, ws2, etc.)
//
void CDockingBar::_ChangeWindowStateAndParent(UINT eModeNew)
{
    LONG ws1, wsx1, ws2, wsx2;
    HWND hwnd;

    if (eModeNew == _eMode) {
        // same mode, nothing to do
        return;
    }

    //
    // nuke old bits
    //
    _GetStyleForMode(_eMode, &ws1, &wsx1, &hwnd);


    //
    // set new bits
    //
    _GetStyleForMode(eModeNew, &ws2, &wsx2, &hwnd);

    // if it's going to be owned by the browser, 
    // override hwnd to our site's hwnd
    if (eModeNew & WBMF_BROWSER)
        hwnd = _hwndSite;

    // style, exstyle
    // (SWB can't do WS_EX_TOPMOST, we do it in caller w/ SWP)
    SHSetWindowBits(_hwnd, GWL_STYLE, ws1|ws2 , ws2);
    SHSetWindowBits(_hwnd, GWL_EXSTYLE, (wsx1|wsx2) & ~WS_EX_TOPMOST, wsx2);

    // id
    // (unchanged)
    HWND hwndParent = GetParent(_hwnd); 
    if (hwndParent != hwnd) {
        if (hwndParent != HWND_DESKTOP) {
            // float->btm, nuke owner
            SHSetParentHwnd(_hwnd, NULL);
        }

        // parent
        SetParent(_hwnd, hwnd);

        if (hwnd == _hwndSite) {
            // btm->float, set owner
            ASSERT(_hwndSite != NULL);
            SHSetParentHwnd(_hwnd, _hwndSite);
        }
    }
    //
    // force redraw
    //
    SetWindowPos(_hwnd, NULL, 0, 0, 0, 0,
        SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);

    return;
}

//***   _SetNewMonitor --
// When the Desktop is our Docking Site, set the new monitor I am on
// and return the old monitor 
HMONITOR CDockingBar::_SetNewMonitor(HMONITOR hMonNew)
{
    HMONITOR hMonOld = NULL;
    if (ISWBM_DESKTOP() && _ptbSite)
    {
        IMultiMonitorDockingSite * pdds;
        HRESULT hresT = _ptbSite->QueryInterface(IID_IMultiMonitorDockingSite, (LPVOID *)&pdds);
        if (SUCCEEDED(hresT))
        {
            HMONITOR hMon;
            ASSERT(pdds);
            if (SUCCEEDED(pdds->GetMonitor(SAFECAST(this, IDockingWindow*), &hMon)))
            {
                if (hMon != hMonNew)
                {
                    pdds->RequestMonitor(SAFECAST(this, IDockingWindow*), &hMonNew);
                    pdds->SetMonitor(SAFECAST(this, IDockingWindow*), hMonNew, &hMonOld);
                    // These two should be the same, otherwise something wierd is happening -- dli
                    ASSERT(hMonOld == hMon);
                }
            }
            pdds->Release();
        }
    }
    
    return hMonOld;
}

//***   _GetBorderRect --
// NOTES
//      result in screen coordinates
//
void CDockingBar::_GetBorderRect(HMONITOR hMon, RECT* prc)
{
    if (!ISWBM_BOTTOM(_eMode)) {
        // FEATURE: todo: should use:
        // floating: _hwndSite (not strictly correct, but good enough)
        // topmost: UnionRect of:
        //     GetWindowRect(_hwndSite);        // non-appbar rect
        //     GetWindowRect(self)              // plus my personal appbar
        ASSERT(IsWindow(_hwndSite));
        if (ISWBM_DESKTOP())
            GetMonitorRect(hMon, prc);
        else
            GetWindowRect(_hwndSite, prc);  
#ifdef DEBUG
#if 0
        RECT rcTmp;

        // these asserts often fail.  e.g. when dragging topmost right->top.
        // weird: _hwndSite ends up being PROGMAN's hwnd.
        // weird: also, the GetWindowRect fails.
        ASSERT(_hwndSite == PARENT_XTOPMOST);
        // _hwndSite is PROGMAN
        GetWindowRect(PARENT_XTOPMOST, &rcTmp);
        ASSERT(EqualRect(prc, &rcTmp));
#endif
#endif
    }
    else if (_ptbSite) {
        HMONITOR hMonOld = _SetNewMonitor(hMon);
        _ptbSite->GetBorderDW(SAFECAST(this, IDockingWindow*), prc);
        if (hMonOld)
            _SetNewMonitor(hMonOld);
        ASSERT(_hwndSite != NULL);
        //ASSERT(GetParent(_hwnd) == _hwndSite);  // FEATURE ISWBM_OWNED?
        // convert if necessary
        // aka ClientToScreen
        MapWindowPoints(_hwndSite, HWND_DESKTOP, (POINT*) prc, 2);
    }

    return;
}

//***   _HideRegister -- (un)register auto-hide w/ edge
// ENTRY/EXIT
//      fToHide         TRUE if turning AutoHide on, FALSE if turning off
//      _fCanHide       [OUT] TRUE if successfully set autohide on; o.w. FALSE 
//      other           pops up dialog if operation fails
//
void CDockingBar::_HideRegister(BOOL fToHide)
{
    BOOL fSuccess;
    APPBARDATA abd;

    if (! ISWBM_HIDEABLE(_eMode))
        return;

    // (try to) register or unregister it
    // n.b. we're allowed to do this even if we're not an AppBar
    // that's good, because we want at most one autohide deskbar 
    // on an edge regardless of mode
    abd.cbSize = SIZEOF(abd);
    abd.hWnd = _hwnd;
    abd.uEdge = _uSide;
    abd.lParam = fToHide;

    // FEATURE should we do a ABM_GETAUTOHIDEBAR at some point?
    // (tray.c does, and so does the AB sample code...)
    //ASSERT(_fAppRegistered);
    fSuccess = (BOOL) SHAppBarMessage(ABM_SETAUTOHIDEBAR, &abd);

    // set our state
    _fCanHide = BOOLIFY(fSuccess);
    // FEATURE: how handle failure?

    // init some stuff
    if (fToHide)
    {
        if (_fCanHide)
        {
            RECT rc;

            ASSERT(_fCanHide);  // so we won't SetVRect

            ASSERT(!_fHiding);  // (paranoia)

            // force a '0-width' rectangle so we don't take up any space
            RectXform(&rc, RX_EDGE|RX_OPPOSE|RX_ADJACENT, &rc, NULL,
                0, _uSide, _hMon);

            switch (_eMode) {
            case WBM_TOPMOST:
                // negotiate/commit it
                APPBARDATA abd;
                abd.cbSize = sizeof(APPBARDATA);
                abd.hWnd = _hwnd;
                ASSERT(_fCanHide);
                // we used to do:
                //  _fCanHide = FALSE;  // hack: so we surrender AppBar's space
                //  AppBarQuerySetPos(&rc, _uSide, &rc, &abd, TRUE);
                //  _fCanHide = TRUE;   // hack: restore
                // but the instant we do the ABSetPos the shell does a recalc
                // by doing a ShowDW of all toolbars, which does a _Recalc,
                // which does a MSH, which ends up doing ProtoRect w/ our
                // 'temporary' _fCanHide=0, which ends up taking space (oops!).
                //
                // so instead we call the low-level ABQueryPos/ABSetPos guys
                // directly.
                AppBarQueryPos(&rc, _uSide, _hMon, &rc, &abd, TRUE);
                AppBarSetPos0(_uSide, &rc, &abd);
                break;
            }

            // do *not* start the hide here
            // it's up to the caller, since a) might want delay or
            // immediate and b) recursion pblms w/ _MoveSizeHelper
        }
        else
        {
            // FEATURE: do PostMessage a la tray.c?
            MLShellMessageBox(_hwnd,
                MAKEINTRESOURCE(IDS_ALREADYAUTOHIDEBAR),
                MAKEINTRESOURCE(IDS_WEBBARTITLE),
                MB_OK | MB_ICONINFORMATION);
            ASSERT(!_fCanHide);
        }
    }
    else
    {
        // do *not* start the unhide here
        // it's up to the caller, since a) might want delay or
        // immediate and b) recursion pblms w/ _MoveSizeHelper

        _fCanHide = FALSE;
    }

    return;
}

//***   IsNearPoint -- am i currently near specified point?
// ENTRY/EXIT
//  pptBase     (INOUT) IN previous cursor pos, OUT updated to current if !fNear
//  fNear       (ret) TRUE if near, o.w. FALSE
// NOTES
//  heuristic stolen from explorer/tray.c!TraySetUnhideTimer
//
BOOL IsNearPoint(/*INOUT*/ POINT *pptBase)
{
    POINT ptCur;
    int dx, dy, dOff, dNear;

    GetCursorPos(&ptCur);
    dx = pptBase->x - ptCur.x;
    dy = pptBase->y - ptCur.y;
    dOff = dx * dx + dy * dy;
    dNear = GetSystemMetrics(SM_CXDOUBLECLK) * GetSystemMetrics(SM_CYDOUBLECLK);
    if (dOff <= dNear)
        return TRUE;
    TraceMsg(DM_HIDE2, "cwb.inp: ret=0 dOff=%d dNear=%d", dOff, dNear);
    *pptBase = ptCur;
    return FALSE;
}

//***   _DoHide --
// DESCRIPTION
//      AHO_KILLDO              kill timer for 'do'   operation
//      AHO_SETDO               set  timer for 'do'   operation
//      AHO_KILLUN              kill timer for 'undo' operation
//      AHO_SETUN               set  timer for 'undo' operation
//      AHO_REG                 register
//      AHO_UNREG               unregister
//      AHO_MOVEDO              do the actual hide
//      AHO_MOVEUN              do the actual unhide
// NOTES
//  the _fIdtXxHide stuff stops us from doing a 2nd SetTimer before the
// 1st one comes in, which makes us never get the 'earlier' ticks.
//  this fixes nt5:142686: drag-over doesn't unhide.  it was caused by us
// getting a bunch of WM_NCHITTESTs in rapid succession (OLE asking us on
// a fast timer?).
//  REARCHITECT: i think there's a tiny race window on _fIdtXxHide (between the
// call to Set/Kill and the shadowing in _fIdtXxHide).  not sure we can
// even hit it, but if we do, i think the worst that happens is somebody
// doesn't hide or unhide for a while.
//
void CDockingBar::_DoHide(UINT uOpMask)
{
    TraceMsg(DM_HIDE, "cwb.dh enter(uOpMask=0x%x(%s))",
        uOpMask, DbMaskToMneStr(uOpMask, AHO_MNE));

    if (!ISWBM_HIDEABLE(_eMode)) {
        TraceMsg(DM_HIDE, "cwb.dh !ISWBM_HIDEABLE(_eMode) => suppress");
        return;
    }

    // nuke old timer
    if (uOpMask & AHO_KILLDO) {
        TraceMsg(DM_HIDE, "cwb.dh: KillTimer(idt_autohide)");
        KillTimer(_hwnd, IDT_AUTOHIDE);
        _fIdtDoHide = FALSE;
    }
    if (uOpMask & AHO_KILLUN) {
        TraceMsg(DM_HIDE, "cwb.dh: KillTimer(idt_autoUNhide)");
        KillTimer(_hwnd, IDT_AUTOUNHIDE);
        _fIdtUnHide = FALSE;
        _ptIdtUnHide.x = _ptIdtUnHide.y = -1;
    }

    if (uOpMask & (AHO_REG|AHO_UNREG)) {
        _HideRegister(uOpMask & AHO_REG);
    }

    if (uOpMask & (AHO_MOVEDO|AHO_MOVEUN)) {
        // tricky, tricky...
        // all the smarts are in _MoveSizeHelper, driven by _fHiding (and _fCanHide)
        // use correct one of (tiny,real)
        _fHiding = (uOpMask & AHO_MOVEDO) ? HIDE_AUTO : FALSE;

        TraceMsg(DM_HIDE, "cwb.dh: move _fHiding=%d", _fHiding);
        ASSERT(_fCanHide);                      // suppress SetVRect
        _Recalc();  // _MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);
    }

    // start new timer
    if (_fCanHide) {
        if (uOpMask & AHO_SETDO) {
            TraceMsg(DM_HIDE, "cwb.dh: SetTimer(idt_autohide) fAlready=%d", _fIdtDoHide);
            if (!_fIdtDoHide) {
                _fIdtDoHide = TRUE;
                SetTimer(_hwnd, IDT_AUTOHIDE, DLY_AUTOHIDE, NULL);
            }
        }
        if (uOpMask & AHO_SETUN) {
            TraceMsg(DM_HIDE, "cwb.dh: SetTimer(idt_autoUNhide) fAlready=%d", _fIdtUnHide);
            // IsNearPoint hysteresis prevents us from unhiding when we happen
            // to be passed over on the way to something unrelated
            if (!IsNearPoint(&_ptIdtUnHide) || !_fIdtUnHide) {
                _fIdtUnHide = TRUE;
                SetTimer(_hwnd, IDT_AUTOUNHIDE, DLY_AUTOUNHIDE, NULL);
            }
        }
    }
    else {
#ifdef DEBUG
        if ((uOpMask & (AHO_SETDO|AHO_SETUN))) {
            TraceMsg(DM_HIDE, "cwb.dh: !_fCanHide => suppress AHO_SET*");
        }
#endif
    }

    return;
}

//***   SlideWindow -- sexy slide effect
// NOTES
//      stolen from tray.c
void SlideWindow(HWND hwnd, RECT *prc, HMONITOR hMonClip, BOOL fShow)
{
    RECT rcMonitor, rcClip;
    BOOL fRegionSet = FALSE; 

    SetRectEmpty(&rcMonitor);
    if (GetNumberOfMonitors() > 1)
    {
        GetMonitorRect(hMonClip, &rcMonitor);
        // aka ScreenToClient
        MapWindowPoints(HWND_DESKTOP, GetParent(hwnd), (LPPOINT)&rcMonitor, 2);     }

    // Future: We could loop on the following code for the slide effect 
    IntersectRect(&rcClip, &rcMonitor, prc);
    if (!IsRectEmpty(&rcClip))
    {
        HRGN hrgnClip;

        // Change the clip region to be relative to the upper left corner of prc
        // NOTE: this is not converting rcClip to prc client coordinate
        OffsetRect(&rcClip, -prc->left, -prc->top);
        
        hrgnClip = CreateRectRgnIndirect(&rcClip);
        // LINTASSERT(hrgnClip || !hgnClip);    // 0 semi-ok for SetWindowRgn
        // nt5:149630: always repaint, o.w. auto-unhide BitBlt's junk
        // from hide position
        fRegionSet = SetWindowRgn(hwnd, hrgnClip, /*fRepaint*/TRUE);
    }
    MoveWindow(hwnd, prc->left, prc->top, RECTWIDTH(*prc), RECTHEIGHT(*prc), TRUE);

    // Turn off the region stuff if we don't hide any more
    if (fRegionSet && fShow)
        SetWindowRgn(hwnd, NULL, TRUE);

    return;
}

/***    AppBarQueryPos -- negotiate position
 * ENTRY/EXIT
 *      return  width (height) from docked edge to opposing edge
 */
int CDockingBar::AppBarQueryPos(RECT* prcOut, UINT uEdge, HMONITOR hMon, const RECT* prcReq,
    PAPPBARDATA pabd, BOOL fCommit)
{
    int iWH;

    ASSERT(ISWBM_DESKTOP());

    // snap to edge (in case another AppBar disappeared w/o us knowing),
    // readjust opposing side to reflect that snap,
    // and max out adjacent sides to fill up full strip.
    iWH = RectGetWH(prcReq, uEdge);
    
    RectXform(&(pabd->rc), RX_EDGE|RX_OPPOSE|RX_ADJACENT|(_fHiding ? RX_HIDE : 0), prcReq, NULL, iWH, uEdge, hMon);

    ASSERT(EqualRect(&(pabd->rc), prcReq));     // caller guarantees?

    // negotiate
    // if we're dragging we might not be registered yet (floating->docked)
    // in that case we'll just use the requested size (w/o negotiating).
    // ditto for if we're in the middle of a top/non-top mode switch.
    if (_fAppRegistered) {
        pabd->uEdge = uEdge;
        TraceMsg(DM_APPBAR, "cwb.abqp: call ABM_QUERYPOS");
        SHAppBarMessage(ABM_QUERYPOS, pabd);
    }

    // readjust opposing side to reflect the negotiation (which only
    // adjusts the moved edge-most side, not the opposing edge).
    // FEATURE: (dli) need to find the right hmonitor to pass  in
    RectXform(prcOut, RX_OPPOSE, &(pabd->rc), NULL, iWH, uEdge, hMon);

    return RectGetWH(prcOut, uEdge);
}

//***   AppBarSetPos --
// NOTES
//      does *not* do _SetVRect and MoveWindow, that's up to caller
//
void CDockingBar::AppBarSetPos(UINT uEdge, const RECT* prcReq, PAPPBARDATA pabd)
{
    ASSERT(_eMode == WBM_TOPMOST);

    if (!_fCanHide && _fAppRegistered)
        AppBarSetPos0(uEdge, prcReq, pabd);

    return;
}

void CDockingBar::AppBarSetPos0(UINT uEdge, const RECT* prcReq, PAPPBARDATA pabd)
{
    CopyRect(&(pabd->rc), prcReq);
    pabd->uEdge = uEdge;

    TraceMsg(DM_APPBAR, "cwb.absp: call ABM_SETPOS");
    ASSERT(_fAppRegistered);
    SHAppBarMessage(ABM_SETPOS, pabd);

    // APPCOMPAT workaround explorer bug: during dragging we get:
    //  querypos*; wm_winposchanged; querypos; setpos
    // the lack of a wm_winposchanged at the end screws up the
    // autohide bring-to-top code.
    ASSERT(pabd->cbSize == sizeof(APPBARDATA));
    ASSERT(pabd->hWnd == _hwnd);
    TraceMsg(DM_APPBAR, "cwb.absp: call ABM_WINPOSCHGED");
    SHAppBarMessage(ABM_WINDOWPOSCHANGED, pabd);

    // n.b. _SetVRect and MoveWindow done by caller

    return;
}

//***   AppBarQuerySetPos --
//
void CDockingBar::AppBarQuerySetPos(RECT* prcOut, UINT uEdge, HMONITOR hMon, const RECT* prcReq,
    PAPPBARDATA pabd, BOOL fCommit)
{
    RECT rcTmp;

    if (prcOut == NULL)
        prcOut = &rcTmp;

    AppBarQueryPos(prcOut, uEdge, hMon, prcReq, pabd, fCommit);
    if (fCommit) {
        AppBarSetPos(uEdge, prcOut, pabd);
        ASSERT(EqualRect(prcOut, &(pabd->rc))); // callers assume prcOut correct
    }

    return;
}

void CDockingBar::_AppBarOnSize()
{
    RECT rc;
    APPBARDATA abd;

    ASSERT(_eMode == WBM_TOPMOST);
    ASSERT(ISABE_DOCK(_uSide));

    if (!_fAppRegistered)
        return;

    // don't commit until done
    if (_fDragging)
        return;

    abd.cbSize = sizeof(APPBARDATA);
    abd.hWnd = _hwnd;

    GetWindowRect(_hwnd, &rc);
    AppBarQuerySetPos(NULL, _uSide, _hMon, &rc, &abd, TRUE);

    return;
}

void CDockingBar::_RemoveToolbar(DWORD dwFlags)
{
    if (_ptbSite) {
        // WM_DESTROY will do _ChangeTopMost(WBM_NIL) for us

        IDockingWindowFrame* ptbframe;
        HRESULT hresT=_ptbSite->QueryInterface(IID_IDockingWindowFrame, (LPVOID*)&ptbframe);
        if (SUCCEEDED(hresT)) {
            AddRef();   // guard against self destruction
            ptbframe->RemoveToolbar(SAFECAST(this, IDockingWindow*), dwFlags);
            ptbframe->Release();
            Release();
        }
    } else {
        CloseDW(0);
    }
}

void CDockingBar::_AppBarOnCommand(UINT idCmd)
{
    UINT eModeNew;

    switch (idCmd) {
    case IDM_AB_TOPMOST:
        eModeNew = _eMode ^ WBM_TOPMOST;
        _MoveSizeHelper(eModeNew, _uSide, _hMon, NULL, NULL, TRUE, TRUE);
        break;

    case IDM_AB_AUTOHIDE:
        if (_fWantHide)
        {
            // on->off
            _DoHide(AHO_KILLDO|AHO_UNREG);      // _ChangeHide
            _fWantHide = FALSE;
        }
        else
        {
            // off->on
            _fWantHide = TRUE;
            // don't do AHO_SETDO now, wait for WM_ACTIVATE(deactivate)
            _DoHide(AHO_REG);     // _ChangeHide
        }

        // force it to happen *now*
        // REARCHITECT potential race condition w/ the AHO_SETDO above,
        // but worst case that should cause a 2nd redraw (?).
        _Recalc();  // _MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);

        if (SHIsChildOrSelf(GetActiveWindow(), _hwnd) != S_OK)
        {
            // nt5:148444: if we're already deactive, we need to kick off
            // the hide now.  this is needed e.g. for login when we load
            // persisted auto-hide deskbars.  they come up inactive so we
            // never get the initial deact to hide them.
            _OnActivate(MAKEWPARAM(WA_INACTIVE, FALSE), (LPARAM)(HWND)0);
        }

        break;
#ifdef DEBUG
    case IDM_AB_ACTIVATE:
        // REARCHITECT temporary until we make browser tell us about activation

        // note that since we're faking this w/ a menu our (normal) assumption
        // in WM_ENTERMENU is bogus so make sure you keep the mouse over
        // the BrowserBar during activation or it will hide away out from under
        // you and the Activate won't work...
        _OnActivate(MAKEWPARAM(_fActive ? WA_INACTIVE : WA_ACTIVE, FALSE),
            (LPARAM) (HWND) 0);
        _fActive = !_fActive;
        break;
#endif

    case IDM_AB_CLOSE:
        _OnCloseBar(TRUE);
        break;

    default:
        MessageBeep(0);
        break;
    }
}

BOOL CDockingBar::_OnCloseBar(BOOL fConfirm)
{
    _RemoveToolbar(0);
    return TRUE;
}

void CDockingBar::_AppBarOnWM(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
    case WM_WINDOWPOSCHANGED:
    case WM_ACTIVATE:
        {
            APPBARDATA abd;

            abd.cbSize = sizeof(APPBARDATA);
            abd.hWnd = _hwnd;
            abd.lParam = (long) NULL;
            if (uMsg == WM_WINDOWPOSCHANGED) {
                TraceMsg(DM_APPBAR, "cwb.WM_WPC: call ABM_WINPOSCHGED");
                SHAppBarMessage(ABM_WINDOWPOSCHANGED, &abd);
            }
            else {
                //if (LOWORD(wParam) != WA_INACTIVE)
                // just do it always, doesn't hurt...
                TraceMsg(DM_APPBAR, "cwb.WM_ACT: call ABM_ACTIVATE");
                SHAppBarMessage(ABM_ACTIVATE, &abd);
            }
        }
        break;

    default:
        ASSERT(0);
        break;
    }

    return;
}

// try to preserve our thinkness
void CDockingBar::_AppBarOnPosChanged(PAPPBARDATA pabd)
{
    RECT rcWindow;

    ASSERT(_eMode == WBM_TOPMOST);

    GetWindowRect(pabd->hWnd, &rcWindow);
    RectXform(&rcWindow, RX_EDGE|RX_OPPOSE, &rcWindow, NULL, RectGetWH(&rcWindow, _uSide), _uSide, _hMon);

    _Recalc();  // _MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);
    return;
}

/***    _InitPos4 -- initialize edge positions
 * ENTRY/EXIT
 *  fCtor       TRUE if called from constructor; o.w. FALSE
 */
void CDockingBar::_InitPos4(BOOL fCtor)
{
    RECT rcSite;

    TraceMsg(DM_PERSIST, "cdb.ip4(fCtor=%d) enter", fCtor);

    if (fCtor)
    {
        // set some worst-case defaults for the Load(bag) case
        _adEdge[ABE_TOP]    = 80;
        _adEdge[ABE_BOTTOM] = 80;
        _adEdge[ABE_LEFT]   = 80;
        _adEdge[ABE_RIGHT]  = 80;

        SetRect(&_rcFloat, 10, 10, 310, 310);       // FEATURE: todo: NYI
        _hMon = GetPrimaryMonitor();
    }
    else
    {
        // set up semi-reasonable defaults for the InitNew case
        ASSERT(_eInitLoaded == IPS_INITNEW);    // not req'd, but expected
        ASSERT(IsWindow(_hwndSite));
        GetWindowRect(_hwndSite, &rcSite);

        _adEdge[ABE_TOP]    = AB_THEIGHT(rcSite);
        _adEdge[ABE_BOTTOM] = AB_BHEIGHT(rcSite);
        _adEdge[ABE_LEFT]   = AB_LWIDTH(rcSite);
        _adEdge[ABE_RIGHT]  = AB_RWIDTH(rcSite);
        
        // FEATURE: (dli) should we ask _hwndSite for it's hmonitor?
        // This current implementation already seems acceptable -justmann
        _hMon = MonitorFromRect(&rcSite, MONITOR_DEFAULTTONULL);
        if (!_hMon)
        {
            POINT ptCenter;
            ptCenter.x = (rcSite.left + rcSite.right) / 2;
            ptCenter.y = (rcSite.top + rcSite.bottom) / 2;
            _hMon = MonitorFromPoint(ptCenter, MONITOR_DEFAULTTONEAREST);
        }

    }

    return;
}

/***    RectXform -- transform RECT
 * ENTRY/EXIT
 *      prcOut
 *      uRxMask
 *      prcIn           initial rect
 *      prcBound        bounding rect specifying min/max dimensions
 *      iWH
 *      uSide
 * DESCRIPTION
 *      RX_EDGE         set edgemost side  to extreme (0 or max)
 *      RX_OPPOSE       set opposing side  to edge + width
 *      RX_ADJACENT     set adjacent sides to extremes (0 and max)
 *      RX_GETWH        get distance to opposing side
 *
 *      Two common calls are:
 *      ...
 * NOTES
 *      Note that rcOut, rcIn, and rcSize can all be the same.
 */
int CDockingBar::RectXform(RECT* prcOut, UINT uRxMask,
    const RECT* prcIn, RECT* prcBound, int iWH, UINT uSide, HMONITOR hMon)
{
    RECT rcDef;
    int  iRet = 0;
    BOOL bMirroredWnd=FALSE;

    if (prcOut != prcIn && prcOut != NULL) {
        ASSERT(prcIn != NULL);  // used to do SetRect(prcOut,0,0,0,0)
        CopyRect(prcOut, prcIn);
    }

#ifdef DEBUG
    if (! (uRxMask & (RX_OPPOSE|RX_GETWH))) {
        ASSERT(iWH == -1);
        iWH = -1;       // try to force something to go wrong...
    }
#endif

    if (uRxMask & (RX_EDGE|RX_ADJACENT)) {
        if (prcBound == NULL) {
            prcBound = &rcDef;
            ASSERT(hMon);
            GetMonitorRect(hMon, prcBound);     // aka GetSystemMetrics(SM_CXSCREEN)
        }

        #define iXMin (prcBound->left)
        #define iYMin (prcBound->top)
        #define iXMax (prcBound->right);
        #define iYMax (prcBound->bottom);
    }

    if (uRxMask & (RX_EDGE|RX_OPPOSE|RX_HIDE|RX_GETWH)) {

        //
        // If docking is happening on a horizontal size, then...
        // 
        if ((ABE_LEFT == uSide) || (ABE_RIGHT == uSide)) {
            bMirroredWnd = (IS_WINDOW_RTL_MIRRORED(GetParent(_hwnd)));
        }

        switch (uSide) {
        case ABE_TOP:
            if (prcOut)
            {
                if (uRxMask & RX_EDGE)
                    prcOut->top = iYMin;
                if (uRxMask & RX_OPPOSE)
                    prcOut->bottom = prcOut->top + iWH;
                if (uRxMask & RX_HIDE)
                    MoveRect(prcOut, prcOut->left, prcOut->top - iWH + CXYHIDE(uSide));
            }
            if (uRxMask & RX_GETWH)
                iRet = RECTHEIGHT(*prcIn);

            break;
        case ABE_BOTTOM:
            if (prcOut)
            {
                if (uRxMask & RX_EDGE)
                    prcOut->bottom = iYMax;
                if (uRxMask & RX_OPPOSE)
                    prcOut->top = prcOut->bottom - iWH;
                if (uRxMask & RX_HIDE)
                    MoveRect(prcOut, prcOut->left, prcOut->bottom - CXYHIDE(uSide));
            }
            if (uRxMask & RX_GETWH)
                iRet = RECTHEIGHT(*prcIn);

            break;
        case ABE_LEFT:
            if (prcOut)
            {
                if (uRxMask & RX_EDGE)
                    prcOut->left = iXMin;
                if (uRxMask & RX_OPPOSE) {
                    //
                    // If the parent of this docked window is mirrored, then it is placed and
                    // aligned to the right. [samera]
                    //
                    if (bMirroredWnd)
                        prcOut->left = prcOut->right - iWH;
                    else
                        prcOut->right = prcOut->left + iWH;
                }
                if (uRxMask & RX_HIDE)
                    MoveRect(prcOut, prcOut->left - iWH + CXYHIDE(uSide), prcOut->top);
            }
            if (uRxMask & RX_GETWH)
                iRet = RECTWIDTH(*prcIn);

            break;
        case ABE_RIGHT:
            if (prcOut)
            {
                if (uRxMask & RX_EDGE)
                    prcOut->right = iXMax;
                if (uRxMask & RX_OPPOSE) {
                    //
                    // If the parent of this docked window is mirrored, then it is placed and
                    // aligned to the left
                    //
                    if (bMirroredWnd)
                        prcOut->right = prcOut->left + iWH;
                    else
                        prcOut->left = prcOut->right - iWH;
                }
                if (uRxMask & RX_HIDE)
                    MoveRect(prcOut, prcOut->right - CXYHIDE(uSide), prcOut->top);
            }
            if (uRxMask & RX_GETWH)
                iRet = RECTWIDTH(*prcIn);

            break;
        }
    }

    if ((uRxMask & RX_ADJACENT) && prcOut)
    {
        if (uSide == ABE_LEFT || uSide == ABE_RIGHT) {
            prcOut->top    = iYMin;
            prcOut->bottom = iYMax;
        }
        else {
            prcOut->left   = iXMin;
            prcOut->right  = iXMax;
        }
    }

    return iRet;
}

//***   _ProtoRect -- create best-guess proto rect for specified location
//
void CDockingBar::_ProtoRect(RECT* prcOut, UINT eModeNew, UINT uSideNew, HMONITOR hMonNew, POINT* ptXY)
{
    if (ISWBM_FLOAT(eModeNew))
    {
        // start at last position/size, and move to new left-top if requested
        CopyRect(prcOut, &_rcFloat);
        if (ptXY != NULL)
            MoveRect(prcOut, ptXY->x, ptXY->y);

        // if we're (e.g.) floating on the far right and the display shrinks,
        // we need to reposition ourselves
        // PERF: wish we could do this at resolution-change time but
        // WM_DISPLAYCHANGE comes in too early (before our [pseudo] parent
        // has changed).
        if (eModeNew == WBM_FLOATING)
        {
            // make sure we're still visible
            // FEATURE todo: multi-mon
            RECT rcTmp;

            _GetBorderRect(hMonNew, &rcTmp);

            if (prcOut->left > rcTmp.right || prcOut->top > rcTmp.bottom)
            {
                // WARNING note we don't explicitly account for other toolbars
                // this may be a bug (though other apps seem to behave the
                // same way)
                MoveRect(prcOut,
                    prcOut->left <= rcTmp.right ? prcOut->left :
                        rcTmp.right - CXFLOAT(),
                    prcOut->top  <= rcTmp.bottom ? prcOut->top  :
                        rcTmp.bottom - CYFLOAT()
                );
            }

        }
    }
    else
    {
        ASSERT(ISABE_DOCK(uSideNew));
        if (_fCanHide && ISWBM_HIDEABLE(eModeNew))
        {
            // force a 'tiny' rectangle
            // (WARNING prcBound==NULL bogus for XXX_HIDEALL && XXX_BROWSEROWNED)
            RectXform(prcOut, RX_EDGE|RX_OPPOSE|RX_ADJACENT|(_fHiding ? RX_HIDE : 0),
                prcOut, NULL, _adEdge[uSideNew], uSideNew, hMonNew);
        }
        else
        {
            // get current rect, adjust opposing side per request
            _GetBorderRect(hMonNew, prcOut);    
            RectXform(prcOut, RX_OPPOSE, prcOut, NULL, _adEdge[uSideNew], uSideNew, hMonNew);

        }
    }

    return;
}

//***   _NegotiateRect --
// NOTES
//      will only return an approximate result in the non-commit case.
//
void CDockingBar::_NegotiateRect(UINT eModeNew, UINT uSideNew, HMONITOR hMonNew,
    RECT* rcReq, BOOL fCommit)
{
    switch (eModeNew) {
    case WBM_TOPMOST:
        APPBARDATA abd;
        abd.cbSize = sizeof(APPBARDATA);
        abd.hWnd = _hwnd;

        AppBarQuerySetPos(rcReq, uSideNew, hMonNew, rcReq, &abd, fCommit);
        if (_fCanHide)
        {
            // we did a query to adjust the adjacent sides (e.g. so we don't
            // cover up the 'start' menu when we unhide).  however that may
            // have also moved us in from the edge, which we don't want.
            // so snap back to edge.
            int iWH;

            iWH = RectGetWH(rcReq, uSideNew);
            RectXform(rcReq, RX_EDGE|RX_OPPOSE|(_fHiding ? RX_HIDE : 0), rcReq, NULL, iWH, uSideNew, hMonNew);
        }
        goto Ldefault;

    default:
    Ldefault:
        // everyone else just gives us what we want

        // but, we need to free up border
        _NegotiateBorderRect(NULL, NULL, fCommit);     // free up space

        break;

    case WBM_BOTTOMMOST:
    case WBM_BBOTTOMMOST:
        _NegotiateBorderRect(rcReq, rcReq, fCommit);
        break;
    }


    return;
}

void CDockingBar::_AppBarCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    APPBARDATA abd;

    ASSERT(_eMode == WBM_TOPMOST);

    abd.cbSize = sizeof(abd);
    abd.hWnd = hwnd;

    switch (wParam) {
    case ABN_FULLSCREENAPP:
        // when 1st  app goes   full-screen, move ourselves to BOTTOM;
        // when last app leaves full-screen, move ourselves back
        // todo: FullScreen(flg)
        {
            BOOL fIsTopmost = BOOLIFY(GetWindowLong(_hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST);
            if (!lParam != fIsTopmost)
            {
                SetWindowPos(hwnd,
                    lParam ? HWND_BOTTOM : HWND_TOPMOST,
                    0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
            }
        }
        break;

    case ABN_POSCHANGED:
        TraceMsg(DM_APPBAR, "cwb.abcb: ABN_POSCHANGED");

        // note that we do this even if _fHiding.  while we want
        // to stay snapped to the edge as a 'tiny' rect, a change
        // in someone else *should* effect our adjacent edges.
        //
        // FEATURE: unfortunately this currently causes 'jiggle' of a hidden
        // guy when another appbar moves (due to a SlideWindow of a 0-width
        // hidden guy and a rounded-up 8-pixel wide guy).  when we switch
        // to explorer's new offscreen hide that should go away.
        _AppBarOnPosChanged(&abd);
        break;
    }

    return;
}

HRESULT CDockingBar::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CDockingBar, IDockingWindow),        // IID_IDockingWindow
        QITABENT(CDockingBar, IObjectWithSite),       // IID_IObjectWithSite
        QITABENT(CDockingBar, IPersistStreamInit),    // IID_IPersistStreamInit
        QITABENTMULTI(CDockingBar, IPersistStream, IPersistStreamInit), // IID_IPersistStream
        QITABENTMULTI(CDockingBar, IPersist, IPersistStreamInit), // IID_IPersist
        QITABENT(CDockingBar, IPersistPropertyBag),   // IID_IPersistPropertyBag
        { 0 },
    };

    HRESULT hres = QISearch(this, qit, riid, ppvObj);

    if (FAILED(hres))
        hres = SUPERCLASS::QueryInterface(riid, ppvObj);

    return hres;
}

HRESULT CDockingBar::QueryService(REFGUID guidService,
                                REFIID riid, void **ppvObj)
{
    HRESULT hres = E_FAIL;
    *ppvObj = NULL; // assume error

    //  Block IID_ITargetFrame, so we don't look like a frame of the
    //  window we are attached to
    if (IsEqualGUID(guidService, IID_ITargetFrame)
        ||IsEqualGUID(guidService, IID_ITargetFrame2)) {
        return hres;
    }

    hres = SUPERCLASS::QueryService(guidService, riid, ppvObj);
    if (FAILED(hres))
    {
        const GUID* pguidService = &guidService;
    
        if (IsEqualGUID(guidService, SID_SProxyBrowser)) {
            pguidService = &SID_STopLevelBrowser;
        }
    
        if (_ptbSite) {
            hres = IUnknown_QueryService(_ptbSite, *pguidService, riid, ppvObj);
        }
    }

    return hres;
}

void CDockingBar::_GrowShrinkBar(DWORD dwDirection)
{
    RECT    rcNew, rcOld;
    int     iMin;

    iMin = GetSystemMetrics(SM_CXVSCROLL) * 4;

    GetWindowRect(_hwnd, &rcNew);   
    rcOld = rcNew;
    
    switch(_uSide)
    {
        case ABE_TOP:
            if (VK_DOWN == dwDirection)
                rcNew.bottom += GetSystemMetrics(SM_CYFRAME);

            if (VK_UP == dwDirection)
                rcNew.bottom -= GetSystemMetrics(SM_CYFRAME);

            if ((rcNew.bottom - rcNew.top) < iMin)
                rcNew.bottom = rcNew.top + iMin;
            break;

        case ABE_BOTTOM:
            if (VK_UP == dwDirection)
                rcNew.top -= GetSystemMetrics(SM_CYFRAME);

            if (VK_DOWN == dwDirection)
                rcNew.top += GetSystemMetrics(SM_CYFRAME);

            if ((rcNew.bottom - rcNew.top) < iMin)
                rcNew.top = rcNew.bottom - iMin;
            break;

        case ABE_LEFT:
            if (VK_RIGHT == dwDirection)
                rcNew.right += GetSystemMetrics(SM_CXFRAME);

            if (VK_LEFT == dwDirection)
                rcNew.right -= GetSystemMetrics(SM_CXFRAME);

            if ((rcNew.right - rcNew.left) < iMin)
                rcNew.right = rcNew.left + iMin;
            break;
            
        case ABE_RIGHT:
            if (VK_LEFT == dwDirection)
                rcNew.left -= GetSystemMetrics(SM_CXFRAME);

            if (VK_RIGHT == dwDirection)
                rcNew.left += GetSystemMetrics(SM_CXFRAME);

            if ((rcNew.right - rcNew.left) < iMin)
                rcNew.left = rcNew.right - iMin;
            break;

    }

    if (!EqualRect(&rcOld, &rcNew))
    {
        int iWH;
        RECT rcScreen;

        // don't let the new size get > MonitorRect/2
        GetMonitorRect(_hMon, &rcScreen);   // aka GetSystemMetrics(SM_CXSCREEN)
        iWH = RECTGETWH(_uSide, &rcScreen);
        iWH /= 2;
        if (RECTGETWH(_uSide, &rcNew) > iWH) 
        {
            RectXform(&rcNew, RX_OPPOSE, &rcNew, NULL, iWH, _uSide, NULL);
        }

        _SetVRect(&rcNew);
        _Recalc();
    }
}


//*** CDockingBar::IOleCommandTarget::* {

HRESULT CDockingBar::Exec(const GUID *pguidCmdGroup,
    DWORD nCmdID, DWORD nCmdexecopt,
    VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
{
    if (pguidCmdGroup == NULL) {
        /*NOTHING*/
    }
    else if (IsEqualGUID(CGID_ShellDocView, *pguidCmdGroup)) {
        switch (nCmdID) {
        case SHDVID_RAISE:
            ASSERT(pvarargIn && pvarargIn->vt == VT_I4);
            if (pvarargIn->vt == VT_I4 && pvarargIn->lVal != DTRF_QUERY) {
                _OnRaise(pvarargIn->lVal);
                return S_OK;
            }
            break;  // e.g. DTRF_QUERY
        default:
            // note that this means we may get OLECMDERR_E_UNKNOWNGROUP
            // rather than OLECMDERR_E_NOTSUPPORTED for unhandled guys...
            break;
        }
    } 
    else if (IsEqualGUID(CGID_DeskBarClient, *pguidCmdGroup)) 
    {
        if (DBCID_RESIZE == nCmdID)
        {
            _GrowShrinkBar(nCmdexecopt);
            return S_OK;
        }
    }
    
    return SUPERCLASS::Exec(pguidCmdGroup, nCmdID, nCmdexecopt,
        pvarargIn, pvarargOut);
}
// }

//*** CDockingBar::IDockingWindow::* {
//

HRESULT CDockingBar::SetSite(IUnknown* punkSite)
{
    ATOMICRELEASE(_ptbSite);

    if (punkSite)
    {
        HRESULT hresT;
        hresT = punkSite->QueryInterface(IID_IDockingWindowSite, (LPVOID*)&_ptbSite);

        IUnknown_GetWindow(punkSite, &_hwndSite);

        //
        // Check if we are under the desktop browser or not and set
        // the initial state correctly. (Always on top for Desktop)
        //
        IUnknown* punkT;
        hresT = punkSite->QueryInterface(SID_SShellDesktop, (LPVOID*)&punkT);
        if (SUCCEEDED(hresT))
        {
            _fDesktop = TRUE;
            punkT->Release();
        }

        if (!_fInitSited)
        {
            if (!_eInitLoaded)
            {
                // if we haven't initialized, do it now.
                InitNew();
                _eMode = WBM_BOTTOMMOST;
            }
                
            ASSERT(_eInitLoaded);
            if (_eInitLoaded == IPS_INITNEW)
            {
                _InitPos4(FALSE);
                _eMode = _fDesktop ? WBM_TOPMOST : WBM_BBOTTOMMOST;
            }
        }
        ASSERT(_eMode != WBM_NIL);
        // WARNING actually we could also be owned floating...
        ASSERT(ISWBM_DESKTOP() == _fDesktop);
        ASSERT(_fDesktop || _eMode == WBM_BBOTTOMMOST);
        ASSERT(ISWBM_DESKTOP() == _fDesktop);
        ASSERT(ISWBM_DESKTOP() || _eMode == WBM_BBOTTOMMOST);
    }

    _fInitSited = TRUE;     // done w/ 1st-time init

    return S_OK;
}

HRESULT CDockingBar::ShowDW(BOOL fShow)
{
    fShow = BOOLIFY(fShow);     // so comparisons and assigns to bitfields work

    // we used to early out if BOOLIFY(_fShow) == fShow.
    // however we now count on ShowDW(TRUE) to force a refresh
    // (e.g. when screen resolution changes CBB::v_ShowHideChildWindows
    // calls us)
    if (BOOLIFY(_fShow) == fShow)
        return S_OK;

    _fShow = fShow;

    if (!_fInitShowed)
    {
        ASSERT(_fInitSited && _eInitLoaded);
        _Initialize();
        ASSERT(_fInitShowed);
    }

    if (_fShow)
    {
        // FEATURE: switch to using _ChangeTopMost, it already does this... 
        // Tell itself to resize.

        // use _MoveSizeHelper (not just _NegotiateBorderRect) since we might
        // actually be moving to a new position...
        _Recalc();  // _MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);
        // _NegotiateBorderRect(NULL, NULL, FALSE)

        if (_pDBC)
            _pDBC->UIActivateDBC(DBC_SHOW);

        // nt5:148444: SW_SHOWNA (vs. SW_SHOW) so we don't unhide on create
        // this fix will cause a new bug -- newly created bars don't have
        // focus (e.g. drag a band to floating, the new floating bar won't
        // have focus) -- but that should be the lesser of evils.
        //ShowWindow(_hwnd, ISWBM_FLOAT(_eMode) ? SW_SHOWNORMAL : SW_SHOWNA);
        ShowWindow(_hwnd, SW_SHOWNA);
        _OnSize();
    }
    else
    {
        ShowWindow(_hwnd, SW_HIDE);
        if (EVAL(_pDBC))
            _pDBC->UIActivateDBC(DBC_SHOWOBSCURE);
        UIActivateIO(FALSE, NULL);
        
        // Tell itself to resize.

        // don't call MoveSizeHelper here since it will do (e.g.)
        // negotiation, which will cause flicker and do destructive stuff.
        //_Recalc();  //_MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);
        _NegotiateBorderRect(NULL, NULL, TRUE);     // hide=>0 border space
    }

    return S_OK;
}

HRESULT CDockingBar::ResizeBorderDW(LPCRECT prcBorder,
    IUnknown* punkToolbarSite, BOOL fReserved)
{
    _Recalc();  // _MoveSizeHelper(_eMode, _uSide, NULL, NULL, TRUE, TRUE);
    return S_OK;    // FEATURE _NegotiateBorderRect()?
}

HRESULT CDockingBar::_NegotiateBorderRect(RECT* prcOut, RECT* prcReq, BOOL fCommit)
{
    UINT eMode, uSide;
    HMONITOR hMon;
    int iWH;

    // FEATURE: should be params like MSH etc.
    eMode = ((_fDragging == DRAG_MOVE) ? _eModePending : _eMode);
    uSide = ((_fDragging == DRAG_MOVE) ? _uSidePending : _uSide);
    hMon = ((_fDragging == DRAG_MOVE) ? _hMonPending : _hMon);

    if (prcOut != prcReq && prcOut != NULL && prcReq != NULL)
        CopyRect(prcOut, prcReq);

    if (_ptbSite) {
        RECT rcRequest = { 0, 0, 0, 0 };

        if (_fShow && ISWBM_BOTTOM(eMode)) {

            if (prcReq)
            {
                iWH = RectGetWH(prcReq, uSide);
                ASSERT(iWH == _adEdge[uSide]);
                if ((!_fCanHide) && uSide != ABE_NIL)
                    ((int*)&rcRequest)[uSide] = iWH;
            }
                
            if (_fTheater) {
                // MOVE TO CBROWSERBAR

                
                // we override the left that we request from the browser, but
                // we need to notify theater what the user has requested for the expaneded width
                VARIANTARG v = { 0 };
                v.vt = VT_I4;
                v.lVal = rcRequest.left;
                IUnknown_Exec(_ptbSite, &CGID_Theater, THID_SETBROWSERBARWIDTH, 0, &v, NULL);
                _iTheaterWidth = v.lVal;
                
                // if we're in theater mode, we can only be on the left and we only grab left border
                ASSERT(uSide == ABE_LEFT);

                // if we're in autohide mode, we request no space
                if (!_fNoAutoHide)
                    rcRequest.left = 0;

                // END MOVE TO CBROWSERBAR                
            }
        }

        // FEATURE: leave alone (at 0 from HideRegister?) if _fHiding==HIDE_AUTO
        HMONITOR hMonOld = _SetNewMonitor(hMon);  

        _ptbSite->RequestBorderSpaceDW(SAFECAST(this, IDockingWindow*), &rcRequest);
        if (fCommit) {
            RECT rcMirRequest;
            LPRECT lprcRequest = &rcRequest; 

            if (IS_WINDOW_RTL_MIRRORED(_hwnd) && 
                !IS_WINDOW_RTL_MIRRORED(GetParent(_hwnd))) {
                // Swap left and right.
                rcMirRequest.left   = rcRequest.right;
                rcMirRequest.right  = rcRequest.left;
                rcMirRequest.top    = rcRequest.top;
                rcMirRequest.bottom = rcRequest.bottom;

                lprcRequest = &rcMirRequest;
            }
            _ptbSite->SetBorderSpaceDW(SAFECAST(this, IDockingWindow*), lprcRequest);
        }

        if (_fShow && ISWBM_BOTTOM(eMode) && !_fTheater) {
            // were'd we end up (as a real rect not just a size)?
            // start w/ our full border area, then apply negotiated width.
            // however that may have also moved us in from the edge, which
            // we don't want if we're autohide, so snap back to edge if so.
            _ptbSite->GetBorderDW(SAFECAST(this, IDockingWindow*), prcOut);

            // aka ClientToScreen
            if (prcOut)
                MapWindowPoints(_hwndSite, HWND_DESKTOP, (POINT*) prcOut, 2);

            if ((!_fCanHide) && uSide != ABE_NIL)
                iWH = ((int*)&rcRequest)[uSide];
            
            RectXform(prcOut, (_fCanHide ? RX_EDGE : 0)|RX_OPPOSE|(_fHiding ? RX_HIDE : 0), prcOut, NULL, iWH, uSide, hMon);
        }
        
        if (hMonOld)
            _SetNewMonitor(hMonOld);

    }

    return S_OK;
}

// }

//*** CDockingBar::IPersistStream*::* {
//

HRESULT CDockingBar::IsDirty(void)
{
    return S_FALSE; // Never be dirty
}

//
// Persisted CDockingBar
//
struct SWebBar
{
    DWORD   cbSize;
    DWORD   cbVersion;
    UINT    uSide : 3;
    UINT    fWantHide :1;
    INT     adEdge[4];  // FEATURE: wordsize dependent
    RECT    rcFloat;    
    POINT   ptSiteCenter; // Center of the docking site -- in case of multiple docking sites

    UINT    eMode;
    UINT    fAlwaysOnTop;

    RECT    rcChild;
};

#define SWB_VERSION 8

HRESULT CDockingBar::Load(IStream *pstm)
{
    SWebBar swb = {0};
    ULONG cbRead;

    TraceMsg(DM_PERSIST, "cwb.l enter(this=%x pstm=%x) tell()=%x", this, pstm, DbStreamTell(pstm));

    ASSERT(!_eInitLoaded);
    HRESULT hres = pstm->Read(&swb, SIZEOF(swb), &cbRead);
#ifdef DEBUG
    // just in case we toast ourselves (offscreen or something)...
    static BOOL fNoPersist = FALSE;
    if (fNoPersist)
        hres = E_FAIL;
#endif
    
    if (hres==S_OK && cbRead==SIZEOF(swb)) {
        // REARCHITECT: this is not forward compatible!
        if (swb.cbSize==SIZEOF(SWebBar) && swb.cbVersion==SWB_VERSION) {

            _eMode = swb.eMode;
            _uSide = swb.uSide;
            _hMon  = MonitorFromPoint(swb.ptSiteCenter, MONITOR_DEFAULTTONEAREST);
            // don't call _SetModeSide, _MoveSizeHelper, etc. until *after* _Initialize
            _fWantHide = swb.fWantHide;
            memcpy(_adEdge, swb.adEdge, SIZEOF(_adEdge));
            _rcFloat = swb.rcFloat;
            _NotifyModeChange(0);

            // child (e.g. bandsite)
            ASSERT(_pDBC != NULL);
            if (_pDBC != NULL) {
                // require IPersistStreamInit?
                IPersistStream *ppstm;
                hres = _pDBC->QueryInterface(IID_IPersistStream, (LPVOID*)&ppstm);
                if (SUCCEEDED(hres)) {

                    // set the child size first because initialization layout might depend on it
                    SetWindowPos(_hwndChild, 0,
                                 swb.rcChild.left, swb.rcChild.top, RECTWIDTH(swb.rcChild), RECTHEIGHT(swb.rcChild),
                                 SWP_NOACTIVATE|SWP_NOZORDER);
                    
                    ppstm->Load(pstm);
                    ppstm->Release();
                }
            }

            _eInitLoaded = IPS_LOAD;    // what if OLFS of bands fails?
            TraceMsg(DM_PERSIST, "CDockingBar::Load succeeded");
        } else {
            TraceMsg(DM_ERROR, "CWB::Load failed swb.cbSize==SIZEOF(SWebBar) && swb.cbVersion==SWB_VERSION");
            hres = E_FAIL;
        }
    } else {
        TraceMsg(DM_ERROR, "CWB::Load failed (hres==S_OK && cbRead==SIZEOF(_adEdge)");
        hres = E_FAIL;
    }
    TraceMsg(DM_PERSIST, "cwb.l leave tell()=%x", DbStreamTell(pstm));
    return hres;
}

HRESULT CDockingBar::Save(IStream *pstm, BOOL fClearDirty)
{
    HRESULT hres;
    SWebBar swb = {0};
    RECT rcMonitor;

    swb.cbSize = SIZEOF(SWebBar);
    swb.cbVersion = SWB_VERSION;
    swb.uSide = _uSide;
    swb.eMode = _eMode;
    swb.fWantHide = _fWantHide;
    memcpy(swb.adEdge, _adEdge, SIZEOF(_adEdge));
    swb.rcFloat = _rcFloat;
    GetWindowRect(_hwndChild, &swb.rcChild);
    MapWindowRect(HWND_DESKTOP, _hwnd, &swb.rcChild);

    ASSERT(_hMon);
    GetMonitorRect(_hMon, &rcMonitor);
    swb.ptSiteCenter.x = (rcMonitor.left + rcMonitor.right) / 2;
    swb.ptSiteCenter.y = (rcMonitor.top + rcMonitor.bottom) / 2;
    
    hres = pstm->Write(&swb, SIZEOF(swb), NULL);
    if (SUCCEEDED(hres))
    {
        IPersistStream* ppstm;
        hres = _pDBC->QueryInterface(IID_IPersistStream, (LPVOID*)&ppstm);
        if (SUCCEEDED(hres))
        {
            hres = ppstm->Save(pstm, TRUE);
            ppstm->Release();
        }
    }
    
    return hres;
}

HRESULT CDockingBar::GetSizeMax(ULARGE_INTEGER *pcbSize)
{
    ULARGE_INTEGER cbMax = { SIZEOF(SWebBar), 0 };
    *pcbSize = cbMax;
    return S_OK;
}

HRESULT CDockingBar::InitNew(void)
{
    ASSERT(!_eInitLoaded);
    _eInitLoaded = IPS_INITNEW;
    TraceMsg(DM_PERSIST, "CDockingBar::InitNew called");

    // can't call _InitPos4 until set site in SetSite
    // don't call _SetModeSide, _MoveSizeHelper, etc. until *after* _Initialize

    // derived class (e.g. CBrowserBarApp) does the _Populate...

    // on first creation, before bands are added, but the bandsite IS created, we need to notify the bandsite of the new position
    _NotifyModeChange(0);
    return S_OK;
}

HRESULT CDockingBar::Load(IPropertyBag *pPropBag, IErrorLog *pErrorLog)
{
    ASSERT(!_eInitLoaded);

    _eInitLoaded = IPS_LOADBAG;

    // TODO: We'll read following properties.
    //
    //  URL = "..."
    //  Mode = 0 - TopMost, 1 - Bottom, 2 - Undocked
    //  Side = 0 - Right, 1 - Top, 2 - Left, 3 - Bottom
    //  Left/Right/Top/Bottom = Initial docked size
    //


    UINT uSide;
    UINT eMode = _eMode;

    if (WBM_NIL == eMode)
        eMode = WBM_BOTTOMMOST;
    
    eMode = PropBag_ReadInt4(pPropBag, L"Mode", eMode);
    uSide = PropBag_ReadInt4(pPropBag, L"Side", _uSide);
    _adEdge[ABE_LEFT] = PropBag_ReadInt4(pPropBag, L"Left", _adEdge[ABE_LEFT]);
    _adEdge[ABE_RIGHT] = PropBag_ReadInt4(pPropBag, L"Right", _adEdge[ABE_RIGHT]);
    _adEdge[ABE_TOP] = PropBag_ReadInt4(pPropBag, L"Top", _adEdge[ABE_TOP]);
    _adEdge[ABE_BOTTOM] = PropBag_ReadInt4(pPropBag, L"Bottom", _adEdge[ABE_BOTTOM]);

    int x = PropBag_ReadInt4(pPropBag, L"X", _rcFloat.left);
    int y = PropBag_ReadInt4(pPropBag, L"Y", _rcFloat.top);
    OffsetRect(&_rcFloat, x - _rcFloat.left, y - _rcFloat.top);

    int cx = PropBag_ReadInt4(pPropBag, L"CX", RECTWIDTH(_rcFloat));
    int cy = PropBag_ReadInt4(pPropBag, L"CY", RECTHEIGHT(_rcFloat));
    _rcFloat.right = _rcFloat.left + cx;
    _rcFloat.bottom = _rcFloat.top + cy;

    // set up vars for eventual CDockingBar::_Initialize call
    ASSERT(!CDB_INITED());
    _eMode = eMode;
    _uSide = uSide;
    
    POINT pt = {x, y};
    // (dli) compute the new hMonitor 
    _hMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);

    // don't call _SetModeSide, _MoveSizeHelper, etc. until *after* _Initialize

    // derived class (e.g. CBrowserBarApp) does the _Populate...

    // on first creation, before bands are added, but the bandsite IS created, we need to notify the bandsite of the new position
    _NotifyModeChange(0);
    return S_OK;
}

HRESULT CDockingBar::Save(IPropertyBag *pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties)
{
    // We don't need to support this for now.
    return E_NOTIMPL;
}

// }

//*** CDockingBar::IDocHostUIHandler::* {
//

HRESULT CDockingBar::ShowContextMenu(DWORD dwID,
    POINT* ppt,
    IUnknown* pcmdtReserved,
    IDispatch* pdispReserved)
{
    if (dwID==0) {
        TraceMsg(DM_MENU, "cdb.scm: intercept");
        return _TrackPopupMenu(ppt);
    }
    return S_FALSE;
}


// }


void CDockingBar::_SetModeSide(UINT eMode, UINT uSide, HMONITOR hMonNew, BOOL fNoMerge) 
{
    _ChangeTopMost(eMode);
    _uSide = uSide;
    _hMon = hMonNew;
    _SetNewMonitor(hMonNew);
}


// *** IInputObjectSite methods ***

HRESULT CDockingBar::OnFocusChangeIS(IUnknown *punk, BOOL fSetFocus)
{
    return IUnknown_OnFocusChangeIS(_ptbSite, SAFECAST(this, IInputObject*), fSetFocus);
}


////////////////////////////////////////////////////////////////
//
//  A deskbar property bag
//////
HRESULT CDockingBarPropertyBag_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    CDockingBarPropertyBag* p = new CDockingBarPropertyBag();
    if (p != NULL)
    {
        *ppunk = SAFECAST(p, IPropertyBag*);
        return S_OK;
    }

    *ppunk = NULL;
    return E_OUTOFMEMORY;
}

ULONG CDockingBarPropertyBag::AddRef()
{
    _cRef++;
    return _cRef;
}

ULONG CDockingBarPropertyBag::Release()
{
    ASSERT(_cRef > 0);
    _cRef--;

    if (_cRef > 0)
        return _cRef;

    delete this;
    return 0;
}

HRESULT CDockingBarPropertyBag::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CDockingBarPropertyBag, IPropertyBag),     // IID_IPropertyBag
        QITABENT(CDockingBarPropertyBag, IDockingBarPropertyBagInit),     // IID_IDockingBarPropertyBagInit
        { 0 },
    };

    return QISearch(this, qit, riid, ppvObj);
}


const WCHAR * const c_szPropNames[] = {
    L"Side",
    L"Mode",
    L"Left",
    L"Top",
    L"Right",
    L"Bottom",
    L"Deleteable",
    L"X",
    L"Y",
    L"CX",
    L"CY"
};


HRESULT CDockingBarPropertyBag::Read( 
                    /* [in] */ LPCOLESTR pszPropName,
                    /* [out][in] */ VARIANT *pVar,
                    /* [in] */ IErrorLog *pErrorLog)
{
    int epropdata;

    for (epropdata = 0; epropdata < (int)PROPDATA_COUNT; epropdata++) {
        if (!StrCmpW(pszPropName, c_szPropNames[epropdata])) {
            break;
        }
    }

    if (epropdata < PROPDATA_COUNT && 
        _props[epropdata]._fSet) {
        pVar->lVal = _props[epropdata]._dwData;
        pVar->vt = VT_I4;
        return S_OK;
    }
    
    return E_FAIL;
}




#ifdef DEBUG
//***   DbCheckWindow --
// NOTES
//  FEATURE: Its a bad idea, why break working code, but here is the suggestion:
//  nuke the 'hwndClient' param and just use GetParent (but what
//  about 'owned' windows, does GetParent give the correct answer?)
BOOL DbCheckWindow(HWND hwnd, RECT *prcExp, HWND hwndClient)
{
    RECT rcAct;

    GetWindowRect(hwnd, &rcAct);
    hwndClient = GetParent(hwnd);   // nuke this param
    if (hwndClient != NULL) {
        // aka ClientToScreen
        MapWindowPoints(HWND_DESKTOP, hwndClient, (POINT*) &rcAct, 2);
    }
    if (!EqualRect(&rcAct, prcExp)) {
        TraceMsg(DM_TRACE,
            "cwb.dbcw: !EqualRect rcAct=(%d,%d,%d,%d) (%dx%d) rcExp=(%d,%d,%d,%d) (%dx%d) hwndClient=0x%x",
            rcAct.left, rcAct.top, rcAct.right, rcAct.bottom,
            RECTWIDTH(rcAct), RECTHEIGHT(rcAct),
            prcExp->left, prcExp->top, prcExp->right, prcExp->bottom,
            RECTWIDTH(*prcExp), RECTHEIGHT(*prcExp),
            hwndClient);
        return FALSE;
    }
    return TRUE;
}

//***   DbStreamTell -- get position in stream (low part only)
//
unsigned long DbStreamTell(IStream *pstm)
{
    if (pstm == 0)
        return (unsigned long) -1;

    ULARGE_INTEGER liEnd;

    pstm->Seek(c_li0, STREAM_SEEK_CUR, &liEnd);
    if (liEnd.HighPart != 0)
        TraceMsg(DM_TRACE, "DbStreamTell: hi!=0");
    return liEnd.LowPart;
}

//***   DbMaskToMneStr -- pretty-print a bit mask in mnemonic form
// ENTRY/EXIT
//  uMask       bit mask
//  szMne       mnemonics, sz[0] for bit 0 .. sz[N] for highest bit
//  return      ptr to *static* buffer
// NOTES
//  n.b.: non-reentrant!!!
TCHAR *DbMaskToMneStr(UINT uMask, TCHAR *szMnemonics)
{
    static TCHAR buf[33];       // FEATURE: non-reentrant!!!
    TCHAR *p;

    p = &buf[ARRAYSIZE(buf) - 1];       // point at EOS
    ASSERT(*p == '\0');
    for (;;) {
        if (*szMnemonics == 0) {
            ASSERT(uMask == 0);
            break;
        }

        --p;
        *p = (uMask & 1) ? *szMnemonics : TEXT('-');

        ++szMnemonics;
        uMask >>= 1;
    }

    return p;
}
#endif
