#include "cabinet.h"
#include "trayclok.h"
#include "traynot.h"
#include "rcids.h"

typedef struct _TNPRIVDATA
{
        HWND hwndNotify;
        HWND hwndClock;
        HWND hwndToolTips;
        RECT rNotifies;
        HDPA hdpaIcons;
        HIMAGELIST himlIcons;
        COLORREF clBk;
        int nCols;
} TNPRIVDATA, *PTNPRIVDATA;

typedef struct _TNPRIVICON
{
        UINT uIMLIndex;
        NOTIFYICONDATA tnd;
} TNPRIVICON, *PTNPRIVICON;


void Tray_SizeWindows();


const TCHAR c_szTrayNotify[] = TEXT("TrayNotifyWnd");

/*
 ** _TNResetToolTipsRects
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

void _TNResetToolTipsRects(PTNPRIVDATA pTNPrivData)
{
        TOOLINFO ti;
        PTNPRIVICON pTNPrivIcon;
        int i, nIcons;
        UINT xIcon, yIcon;
        int x, y;

        // change all the rects for the tool tips mgr.  this is
        // cheap, and we don't do it often, so go ahead
        // and do them all.
        if(!pTNPrivData->hwndToolTips)
        {
                return;
        }

        xIcon = GetSystemMetrics(SM_CXSMICON);
        yIcon = GetSystemMetrics(SM_CYSMICON);

        x = pTNPrivData->rNotifies.left;
        y = pTNPrivData->rNotifies.top;

        ti.cbSize = SIZEOF(ti);
        ti.uFlags = 0;
        ti.hwnd = pTNPrivData->hwndNotify;
        ti.lpszText = LPSTR_TEXTCALLBACK;

        nIcons = DPA_GetPtrCount(pTNPrivData->hdpaIcons);
        for (i=0; i<nIcons; )
        {
                pTNPrivIcon = DPA_FastGetPtr(pTNPrivData->hdpaIcons, i);

                ti.uId = (UINT)pTNPrivIcon;
                ti.rect.left = x;
                ti.rect.top = y;
                ti.rect.right = x + xIcon;
                ti.rect.bottom = y + yIcon;
                SendMessage(pTNPrivData->hwndToolTips, TTM_NEWTOOLRECT,
                        0, (LPARAM)((LPTOOLINFO)&ti));

                x += xIcon + g_cxBorder;

                ++i;
                if (i%pTNPrivData->nCols == 0)
                {
                        y += yIcon + g_cyBorder;
                        x = pTNPrivData->rNotifies.left;
                }
        }
}


/*
 ** _TNRemoveImage
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

void _TNRemoveImage(PTNPRIVDATA pTNPrivData, UINT uIMLIndex)
{
        int nIcon;
        PTNPRIVICON pTNPrivIcon;

        if (uIMLIndex != (UINT)-1)
            ImageList_Remove(pTNPrivData->himlIcons, uIMLIndex);

        // Adjust the ImageList indices for all other icons
        for (nIcon=DPA_GetPtrCount(pTNPrivData->hdpaIcons)-1; nIcon>=0; --nIcon)
        {
                pTNPrivIcon = DPA_GetPtr(pTNPrivData->hdpaIcons, nIcon);
                if (pTNPrivIcon->uIMLIndex > uIMLIndex)
                {
                        --pTNPrivIcon->uIMLIndex;
                }
        }
}


/*
 ** _TNFindNotify
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

int _TNFindNotify(PTNPRIVDATA pTNPrivData, PNOTIFYICONDATA pTND)
{
        int i;
        PTNPRIVICON pTNPrivIcon;

        for (i=DPA_GetPtrCount(pTNPrivData->hdpaIcons)-1; i>=0; --i)
        {
                pTNPrivIcon = DPA_GetPtr(pTNPrivData->hdpaIcons, i);
                if (pTNPrivIcon->tnd.hWnd==pTND->hWnd && pTNPrivIcon->tnd.uID==pTND->uID)
                {
                        break;
                }
        }

        return(i);
}

//---------------------------------------------------------------------------
// Returns TRUE if either the images are OK as they are or they needed
// resizing and the resize process worked. FALSE otherwise.
BOOL _TNCheckAndResizeImages(PTNPRIVDATA pTNPrivData)
{
    HIMAGELIST himlOld, himlNew;
    int cxSmIconNew, cySmIconNew, cxSmIconOld, cySmIconOld;
    int i, cItems;
    COLORREF clBkNew;
    HICON hicon;
    BOOL fOK = TRUE;
            
    if (!pTNPrivData)
        return 0;

    himlOld = pTNPrivData->himlIcons;

    // Do dimensions match current icons?
    cxSmIconNew = GetSystemMetrics(SM_CXSMICON);
    cySmIconNew = GetSystemMetrics(SM_CYSMICON);
    ImageList_GetIconSize(himlOld, &cxSmIconOld, &cySmIconOld);
    if (cxSmIconNew != cxSmIconOld || cxSmIconNew != cxSmIconOld)
    {
        // Nope, we're gonna need a new imagelist.
        himlNew = ImageList_Create(cxSmIconNew, cySmIconNew, TRUE, 0, 1);
        if (himlNew)
        {
            clBkNew = GetSysColor(COLOR_3DFACE);
            ImageList_SetBkColor(himlNew, clBkNew);

            // Copy the images over to the new image list.
            cItems = ImageList_GetImageCount(himlOld);
            for (i = 0; i < cItems; i++)
            {
                // REVIEW Lame - there's no way to copy images to an empty 
                // imagelist, resizing it on the way.
                hicon = ImageList_GetIcon(himlOld, i, ILD_NORMAL);
                if (hicon)
                {
                    if (ImageList_AddIcon(himlNew, hicon) == -1)
                    {
                        // Couldn't copy image so bail.
                        fOK = FALSE;
                    }
                    DestroyIcon(hicon);
                }
                else
                {
                    fOK = FALSE;
                }

                // FU - bail.
                if (!fOK)
                    break;
            }
            
            // Did everything copy over OK?
            if (fOK)
            {
                // Yep, Set things up to use the new one.
                pTNPrivData->himlIcons = himlNew;
                // Destroy the old icon cache.
                ImageList_Destroy(himlOld);
            }
            else
            {
                // Nope, stick with what we have.
                ImageList_Destroy(himlNew);
            }
        }
    }

    return fOK;     
}


/*
 ** _TNModifyNotify
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

BOOL _TNModifyNotify(PTNPRIVDATA pTNPrivData, PNOTIFYICONDATA pnid, int nIcon)
{
        PTNPRIVICON pTNPrivIcon;
        UINT uIMLIndex, uIMLNew;

        pTNPrivIcon = DPA_GetPtr(pTNPrivData->hdpaIcons, nIcon);
        if (!pTNPrivIcon)
        {
                return(FALSE);
        }

        _TNCheckAndResizeImages(pTNPrivData);
    
        // The icon is the only thing that can fail, so I will do it first
        if (pnid->uFlags&NIF_ICON)
        {
                if (pnid->hIcon)
                {
                        // Replace icon knows how to handle -1 for add
                        uIMLNew = ImageList_ReplaceIcon(pTNPrivData->himlIcons,
                                     pTNPrivIcon->uIMLIndex, pnid->hIcon);
                        if ((int)uIMLNew < 0)
                        {
                                return(FALSE);
                        }
                }
                else
                {
                        _TNRemoveImage(pTNPrivData, pTNPrivIcon->uIMLIndex);
                        uIMLNew = (UINT)-1;
                }
                pTNPrivIcon->uIMLIndex = uIMLNew;
        }

        if (pnid->uFlags&NIF_MESSAGE)
        {
                pTNPrivIcon->tnd.uCallbackMessage = pnid->uCallbackMessage;
        }
        if (pnid->uFlags&NIF_TIP)
        {
                lstrcpyn(pTNPrivIcon->tnd.szTip, pnid->szTip,
                        ARRAYSIZE(pTNPrivIcon->tnd.szTip));
        }
        return(TRUE);
}


/*
 ** _TNDeleteNotify
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

BOOL _TNDeleteNotify(PTNPRIVDATA pTNPrivData, int nIcon)
{
        PTNPRIVICON pTNPrivIcon;
        UINT uIMLIndex;

        pTNPrivIcon = DPA_GetPtr(pTNPrivData->hdpaIcons, nIcon);
        if (!pTNPrivIcon)
        {
                return(FALSE);
        }

        DPA_DeletePtr(pTNPrivData->hdpaIcons, nIcon);

        if(pTNPrivData->hwndToolTips)
        {
                TOOLINFO ti;
                ti.cbSize = SIZEOF(ti);
                ti.hwnd = pTNPrivData->hwndNotify;
                ti.uId = (UINT)pTNPrivIcon;
                SendMessage(pTNPrivData->hwndToolTips, TTM_DELTOOL,
                        0, (LPARAM)(LPTOOLINFO)&ti);
        }

        uIMLIndex = pTNPrivIcon->uIMLIndex;
        _TNRemoveImage(pTNPrivData, uIMLIndex);

        LocalFree((HLOCAL)pTNPrivIcon);

        return(TRUE);
}


/*
 ** _TNInsertNotify
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

BOOL _TNInsertNotify(PTNPRIVDATA pTNPrivData, PNOTIFYICONDATA pnid, int insert)
{
        PTNPRIVICON pTNPrivIcon;

        // First insert a totally "default" icon
        pTNPrivIcon = LocalAlloc(LPTR, SIZEOF(TNPRIVICON));
        if (!pTNPrivIcon)
        {
                goto Error1;
        }

        pTNPrivIcon->uIMLIndex = (UINT)-1;
        pTNPrivIcon->tnd.hWnd = pnid->hWnd;
        pTNPrivIcon->tnd.uID = pnid->uID;

        insert = DPA_InsertPtr(pTNPrivData->hdpaIcons, insert, pTNPrivIcon);
        if (insert == -1)
        {
                goto Error2;
        }

        if(pTNPrivData->hwndToolTips)
        {
                TOOLINFO ti;

                // don't bother setting the rect because we'll do it soon
                // anyway later;
                ti.cbSize = SIZEOF(ti);
                ti.uFlags = 0;
                ti.hwnd = pTNPrivData->hwndNotify;
                ti.uId = (UINT)pTNPrivIcon;
                ti.lpszText = LPSTR_TEXTCALLBACK;
                SendMessage(pTNPrivData->hwndToolTips, TTM_ADDTOOL,
                        0, (LPARAM)(LPTOOLINFO)&ti);
        }

        // Then modify this icon with the specified info
        if (!_TNModifyNotify(pTNPrivData, pnid, insert))
        {
                _TNDeleteNotify(pTNPrivData, insert);
                // Note that we do not go to the LocalFree
                goto Error1;
        }

        return(TRUE);

Error2:
        LocalFree((HLOCAL)pTNPrivIcon);
Error1:
        return(FALSE);
}


/*
 ** _TNCreate
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT _TNCreate(HWND hWnd)
{
        HWND hwndClock;
        PTNPRIVDATA pTNPrivData;

        hwndClock = ClockCtl_Create(hWnd, IDC_CLOCK, hinstCabinet);
        if (!hwndClock)
        {
                return(-1);
        }

        pTNPrivData = (PTNPRIVDATA)LocalAlloc(LPTR, SIZEOF(TNPRIVDATA));
        if (!pTNPrivData)
        {
                return(-1);
        }
        SetWindowLong(hWnd, 0, (LONG)pTNPrivData);

        pTNPrivData->hwndToolTips = CreateWindowEx(WS_EX_TOPMOST, c_szSToolTipsClass, c_szNULL,
                WS_POPUP | TTS_ALWAYSTIP,
                CW_USEDEFAULT, CW_USEDEFAULT,
                CW_USEDEFAULT, CW_USEDEFAULT,
                NULL, NULL, hinstCabinet,
                NULL);
    
        pTNPrivData->hwndNotify = hWnd;
        pTNPrivData->hwndClock = hwndClock;
        pTNPrivData->hdpaIcons = DPA_Create(0);
        if (!pTNPrivData->hdpaIcons)
        {
                return(-1);
        }
        pTNPrivData->himlIcons = ImageList_Create(
                GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
                ILC_MASK, 0, 1);
        if (!pTNPrivData->himlIcons)
        {
                return(-1);
        }

        pTNPrivData->clBk = GetSysColor(COLOR_3DFACE);
        ImageList_SetBkColor(pTNPrivData->himlIcons, pTNPrivData->clBk);

        return(0);
}


/*
 ** _TNDestroy
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT _TNDestroy(PTNPRIVDATA pTNPrivData)
{
        if (pTNPrivData)
        {
            
            if (pTNPrivData->hwndToolTips) 
                DestroyWindow(pTNPrivData->hwndToolTips);
            
                if (pTNPrivData->hdpaIcons)
                {
                        while (_TNDeleteNotify(pTNPrivData, 0))
                        {
                                // Continue while there are icondata's to delete
                        }
                        DPA_Destroy(pTNPrivData->hdpaIcons);
                }

                if (pTNPrivData->himlIcons)
                {
                        ImageList_Destroy(pTNPrivData->himlIcons);
                }

                SetWindowLong(pTNPrivData->hwndNotify, 0, 0);
                LocalFree((HLOCAL)pTNPrivData);
        }

        return(0);
}


/*
 ** _TNPaint
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT _TNPaint(PTNPRIVDATA pTNPrivData)
{
        HDC hdc;
        PAINTSTRUCT ps;
        UINT xIcon, yIcon;
        RECT rcClient;
        int x, y, i;
        PTNPRIVICON pTNPrivIcon;
        int nIcons;
        COLORREF clBk;
        HWND hWnd = pTNPrivData->hwndNotify;

        hdc = BeginPaint(hWnd, &ps);

        clBk = GetSysColor(COLOR_3DFACE);
        if (pTNPrivData->clBk != clBk)
        {
                pTNPrivData->clBk = clBk;
                ImageList_SetBkColor(pTNPrivData->himlIcons, pTNPrivData->clBk);
        }

        GetClientRect(hWnd, &rcClient);

        // Draw the edge right away to reduce flash
        // DrawEdge(hdc, &rcClient, BDR_SUNKENOUTER, BF_RECT);

        xIcon = GetSystemMetrics(SM_CXSMICON);
        yIcon = GetSystemMetrics(SM_CYSMICON);

        x = pTNPrivData->rNotifies.left;
        y = pTNPrivData->rNotifies.top;

        nIcons = DPA_GetPtrCount(pTNPrivData->hdpaIcons);
        for (i=0; i<nIcons; )
        {
                pTNPrivIcon = DPA_FastGetPtr(pTNPrivData->hdpaIcons, i);

                // Note that if uIMLIndex < 0, this will not paint anything
                if (pTNPrivIcon->uIMLIndex != (UINT)-1)
                    ImageList_Draw(pTNPrivData->himlIcons,
                        pTNPrivIcon->uIMLIndex, hdc, x, y, 0);
                x += xIcon + g_cxBorder;

                ++i;
                if (i%pTNPrivData->nCols == 0)
                {
                        y += yIcon + g_cyBorder;
                        x = pTNPrivData->rNotifies.left;
                }
        }

        EndPaint(hWnd, &ps);

        return(0);
}


/*
 ** _TNCalcRects
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *      This is kind of strange: the RECT for the clock is exact, while the
 *      RECT for the icons is exact on the left and top, but adds an extra
 *      border on the right and bottom.
 *
 *  RETURNS:
 *
 */

UINT _TNCalcRects(PTNPRIVDATA pTNPrivData, int nMaxHorz, LPRECT prClock, LPRECT prNotifies)
{
        UINT xIcon, yIcon;
        UINT nCols, nRows;
        LRESULT lRes;

        xIcon = GetSystemMetrics(SM_CXSMICON);
        yIcon = GetSystemMetrics(SM_CYSMICON);

        lRes = SendMessage(pTNPrivData->hwndClock, WM_CALCMINSIZE, 0, 0);
        prClock->right = LOWORD(lRes);
        prClock->bottom = HIWORD(lRes);
    
        // We need an g_cxBorder between each icon, plus a border between the
        // icons and the clock, and the icons and the edge (if any icons)
        prNotifies->right = DPA_GetPtrCount(pTNPrivData->hdpaIcons) * (xIcon+g_cxBorder);
        if (prNotifies->right)
        {
                prNotifies->right += g_cxBorder;
        }

        // If there are no icons, or if the extent is wide enough,
        // then we will just have one row of information
        if (!prNotifies->right || nMaxHorz>=prNotifies->right+prClock->right)
        {
                if (prNotifies->right)
                {
                        prNotifies->left = g_cxBorder;
                        prNotifies->top = g_cyBorder;
                        // right is already set
                        prNotifies->bottom = prNotifies->top + yIcon + g_cyBorder;
                }
                else
                {
                        SetRectEmpty(prNotifies);
                }

                prClock->left = prNotifies->right;
                prClock->top = 0;
                prClock->right += prClock->left;
                prClock->bottom += prClock->top;

                nCols = DPA_GetPtrCount(pTNPrivData->hdpaIcons);
        }
        else
        {
                // Adjust for one border width around the icons
                nMaxHorz -= 2*g_cxBorder;

                // We need to fit at least one icon
                if (nMaxHorz < (int)xIcon)
                {
                        nMaxHorz = xIcon;
                }

                // Find the number of icons that will fit across, and thus
                // the number of rows
                nCols = (nMaxHorz+g_cxBorder)/(xIcon+g_cxBorder);
                nRows = (DPA_GetPtrCount(pTNPrivData->hdpaIcons)+nCols-1)/nCols;

                prNotifies->left = g_cxBorder;
                prNotifies->top = prClock->bottom + g_cyBorder;
                // Add the border around the edges
                prNotifies->right = nCols*(xIcon+g_cxBorder) + g_cxBorder;
                prNotifies->bottom = prNotifies->top
                        + nRows*(yIcon+g_cyBorder) + g_cyBorder;

                prClock->left = 0;
                prClock->top = 0;
                if (prClock->right && (prClock->right < prNotifies->right))
                {
                        // Use the larger value to center properly
                        prClock->right = prNotifies->right;
                }
                // bottom is already set
        }

        if (prClock->bottom < g_cySize + g_cyEdge)
            prClock->bottom = g_cySize + g_cyEdge;

        // Add back the border around the whole window
        OffsetRect(prClock, g_cxBorder, g_cyBorder);
        OffsetRect(prNotifies, g_cxBorder, g_cyBorder);

        return(nCols);
}


/*
 ** _TNCalcMinSize
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT _TNCalcMinSize(PTNPRIVDATA pTNPrivData, int nMaxHorz)
{
    RECT rTotal, rClock, rNotifies;

    if (!(GetWindowLong(pTNPrivData->hwndClock, GWL_STYLE) & WS_VISIBLE) &&
        !DPA_GetPtrCount(pTNPrivData->hdpaIcons)) {
        ShowWindow(pTNPrivData->hwndNotify, SW_HIDE);
        return 0L;
    } else {
        if (!IsWindowVisible(pTNPrivData->hwndNotify))
            ShowWindow(pTNPrivData->hwndNotify, SW_SHOW);
    }
    
    pTNPrivData->nCols = _TNCalcRects(pTNPrivData, nMaxHorz, &rClock, &rNotifies);

    UnionRect(&rTotal, &rClock, &rNotifies);
    
    // this can happen if rClock's hidden width is 0;
    // make sure the rTotal height is at least the clock's height.
    // it can be smaller if the clock is hidden and thus has a 0 width
    if ((rTotal.bottom - rTotal.top) < (rClock.bottom - rClock.top))
        rTotal.bottom = rTotal.top + (rClock.bottom - rClock.top);

    // Add on room for borders
    return(MAKELRESULT(rTotal.right+g_cxBorder, rTotal.bottom+g_cyBorder));
}


/*
 ** _TNSize
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT _TNSize(PTNPRIVDATA pTNPrivData)
{
        RECT rTotal, rClock;
        HWND hWnd = pTNPrivData->hwndNotify;

        // use GetWindowRect because _TNCalcRects includes the borders
        GetWindowRect(hWnd, &rTotal);
        rTotal.right -= rTotal.left;
        rTotal.bottom -= rTotal.top;
        rTotal.left = rTotal.bottom = 0;

        // Account for borders on the left and right
        pTNPrivData->nCols = _TNCalcRects(pTNPrivData, rTotal.right,
                &rClock, &pTNPrivData->rNotifies);

        SetWindowPos(pTNPrivData->hwndClock, NULL, rClock.left, rClock.top,
                rClock.right-rClock.left, rClock.bottom-rClock.top, SWP_NOZORDER);

        _TNResetToolTipsRects(pTNPrivData);

        return(0);
}

// returns BOOL if the lParam specifies a pos over the clock
extern BOOL IsPosInHwnd(LPARAM lParam, HWND hwnd);

#define _IsOverClock(pTNPrivdata, lParam) IsPosInHwnd(lParam, pTNPrivdata->hwndClock)

/*
 ** _TNIconFromPoint
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *      Note that a click on the C?BORDER after an icon yields a click on
 *      that icon.  No big deal.
 *
 *  RETURNS:
 *
 */

PTNPRIVICON _TNIconFromPoint(PTNPRIVDATA pTNPrivData, int x, int y, LPINT pnIcon)
{
        UINT xIcon, yIcon;
        int nCol, nRow, nIcon;

        xIcon = GetSystemMetrics(SM_CXSMICON);
        yIcon = GetSystemMetrics(SM_CYSMICON);

        if (!pTNPrivData->nCols)
        {
                return(NULL);
        }

        x -= pTNPrivData->rNotifies.left;
        nCol = x / (xIcon+g_cxBorder);
        // Make sure that x is in bounds
        if (x<0 || nCol>=pTNPrivData->nCols)
        {
                return(NULL);
        }

        y -= pTNPrivData->rNotifies.top;
        nRow = y / (yIcon+g_cyBorder);
        // Make sure that y is in bounds
        if (y < 0)
        {
                return(NULL);
        }

        nIcon = pTNPrivData->nCols*nRow + nCol;

        *pnIcon = nIcon;
        // We will get NULL if we go off the end of the array
        return(DPA_GetPtr(pTNPrivData->hdpaIcons, nIcon));
}


/*
 ** _TNMouseEvent
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT _TNMouseEvent(PTNPRIVDATA pTNPrivData, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        PTNPRIVICON pTNPrivIcon;
        int nIcon;

        if(pTNPrivData->hwndToolTips)
        {
                MSG msg;

                msg.lParam = lParam;
                msg.wParam = wParam;
                msg.message = uMsg;
                msg.hwnd = pTNPrivData->hwndNotify;
                SendMessage(pTNPrivData->hwndToolTips, TTM_RELAYEVENT,
                        0, (LPARAM)(LPMSG)&msg);
        }

        pTNPrivIcon = _TNIconFromPoint(pTNPrivData, LOWORD(lParam), HIWORD(lParam), &nIcon);
        if (!pTNPrivIcon)
        {
                return(0);
        }

        if (pTNPrivIcon->tnd.uCallbackMessage)
        {
            if (IsWindow(pTNPrivIcon->tnd.hWnd)) {
                SendNotifyMessage(pTNPrivIcon->tnd.hWnd,
                        pTNPrivIcon->tnd.uCallbackMessage, pTNPrivIcon->tnd.uID,
                        uMsg);
            } else {
                _TNDeleteNotify(pTNPrivData, nIcon);
                Tray_SizeWindows();
            }
            return 1;
        }

        return(0);
}


LRESULT _TNNotify(PTNPRIVDATA pTNPrivData, LPNMHDR lpNmhdr)
{
        LPTOOLTIPTEXT pTtt;
        PTNPRIVICON pTNPrivIcon;

        switch (lpNmhdr->code)
        {
            
        case TTN_SHOW:
            SetWindowPos(pTNPrivData->hwndToolTips,
                         HWND_TOP,
                         0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
            break;
            
        case TTN_NEEDTEXT:
                pTtt = (LPTOOLTIPTEXT)lpNmhdr;
                pTNPrivIcon = (PTNPRIVICON)pTtt->hdr.idFrom;

                lstrcpyn(pTtt->lpszText, pTNPrivIcon->tnd.szTip,
                        ARRAYSIZE(pTtt->szText));
                break;

        default:
                break;
        }

        return(0);
}

//---------------------------------------------------------------------------
LRESULT CALLBACK _TNWinIniChange(PTNPRIVDATA pTNPrivData, UINT uMsg, 
        WPARAM wParam, LPARAM lParam)
{

    _TNCheckAndResizeImages(pTNPrivData);

    return(pTNPrivData && pTNPrivData->hwndClock && 
        SendMessage(pTNPrivData->hwndClock,
        uMsg, wParam, lParam));
}

//---------------------------------------------------------------------------
LRESULT CALLBACK TrayNotifyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        PTNPRIVDATA pTNPrivData;

        pTNPrivData = (PTNPRIVDATA)GetWindowLong(hWnd, 0);

        switch (uMsg)
        {
        case WM_CREATE:
                return _TNCreate(hWnd);
        case WM_DESTROY:
                return _TNDestroy(pTNPrivData);
        case WM_PAINT:
                return _TNPaint(pTNPrivData);

        case WM_CALCMINSIZE:
                return _TNCalcMinSize(pTNPrivData, wParam);

        // The clock needs to see certain messages forwarded to him.
        case WM_TIMECHANGE:
        case WM_WININICHANGE:
                return _TNWinIniChange(pTNPrivData, uMsg, wParam, lParam);
                

        case WM_NCHITTEST:
                return(_IsOverClock(pTNPrivData, lParam) ? HTTRANSPARENT : HTCLIENT);


        case WM_LBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_RBUTTONDOWN:
        case WM_MOUSEMOVE:
        case WM_LBUTTONUP:
        case WM_MBUTTONUP:
        case WM_RBUTTONUP:
        case WM_LBUTTONDBLCLK:
        case WM_MBUTTONDBLCLK:
        case WM_RBUTTONDBLCLK:
                   if (_TNMouseEvent(pTNPrivData, uMsg, wParam, lParam) && (uMsg == WM_RBUTTONUP))
                        break;
               goto DoDefault;  // we need to pass these through to defwndproc for user to process WM_CONTEXTMENU

        case WM_NOTIFY:
                return(_TNNotify(pTNPrivData, (LPNMHDR)lParam));

        case TNM_GETCLOCK:
            return (LRESULT)pTNPrivData->hwndClock;
            
        case TNM_TRAYHIDE:
            if (IsWindowVisible(pTNPrivData->hwndClock))
                SendMessage(pTNPrivData->hwndClock, TCM_TRAYHIDE, 0, lParam);
            break;
            
        case TNM_HIDECLOCK:
            ShowWindow(pTNPrivData->hwndClock, lParam ? SW_HIDE : SW_SHOW);
            if (!lParam) {
                PostMessage(pTNPrivData->hwndClock, TCM_KICKSTART, 0, 0);
            }
            break;
        case WM_SIZE:
                _TNSize(pTNPrivData);
        case WM_MOVE:
                // Always invalidate on a move or resize
                InvalidateRect(hWnd, NULL, TRUE);
        default:
        DoDefault:
                return (DefWindowProc(hWnd, uMsg, wParam, lParam));
        }

        return 0;
}


#if 0
/*
 ** _TNGetIconRect
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

void _TNGetIconRect(PTNPRIVDATA pTNPrivData, int nIcon, LPRECT prIcon)
{
        UINT xIcon, yIcon;

        xIcon = GetSystemMetrics(SM_CXSMICON);
        yIcon = GetSystemMetrics(SM_CYSMICON);

        if (!pTNPrivData->nCols)
        {
                SetRectEmpty(prIcon);
                return;
        }

        prIcon->left = (nIcon%pTNPrivData->nCols) * (xIcon+g_cxBorder);
        prIcon->top  = (nIcon/pTNPrivData->nCols) * (yIcon+g_cyBorder);
        prIcon->right  = prIcon->left + xIcon+g_cxBorder;
        prIcon->bottom = prIcon->top  + yIcon+g_cyBorder;

        OffsetRect(prIcon, pTNPrivData->rNotifies.left, pTNPrivData->rNotifies.top);
}
#endif


/*
 ** TrayNotify
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

LRESULT TrayNotify(HWND hwndNotify, HWND hwndFrom, PCOPYDATASTRUCT pcds)
{
        int nIcon;
        PTNPRIVDATA pTNPrivData;
        RECT rNotifies;
        PTRAYNOTIFYDATA pTND;
        PNOTIFYICONDATA pNID;
        BOOL bErase = TRUE;

        if (!hwndNotify || !pcds)
        {
                return(FALSE);
        }

        pTNPrivData = (PTNPRIVDATA)GetWindowLong(hwndNotify, 0);

        if (pcds->cbData < SIZEOF(TRAYNOTIFYDATA))
        {
                return(FALSE);
        }

        // We'll add a signature just in case
        pTND = (PTRAYNOTIFYDATA)pcds->lpData;
        if (pTND->dwSignature != NI_SIGNATURE)
        {
                return(FALSE);
        }

        pNID = &pTND->nid;
        if (pNID->cbSize<SIZEOF(NOTIFYICONDATA))
        {
                return(FALSE);
        }

        rNotifies = pTNPrivData->rNotifies;

        switch (pTND->dwMessage)
        {
        case NIM_ADD:
                if (_TNFindNotify(pTNPrivData, pNID) >= 0)
                {
                        return(FALSE);
                }

                if (!_TNInsertNotify(pTNPrivData, pNID, 0x7fff))
                {
                        return(FALSE);
                }
                break;

        case NIM_MODIFY:
                nIcon = _TNFindNotify(pTNPrivData, pNID);
                if (nIcon < 0)
                {
                        return(FALSE);
                }

                if (!_TNModifyNotify(pTNPrivData, pNID, nIcon))
                {
                        return(FALSE);
                }

                bErase = FALSE;
                break;

        case NIM_DELETE:
                nIcon = _TNFindNotify(pTNPrivData, pNID);
                if (nIcon < 0)
                {
                        return(FALSE);
                }

                _TNDeleteNotify(pTNPrivData, nIcon);
                break;

        default:
                return(FALSE);
        }

        // Invalidate the old and new rectangle that includes all the icons
        InvalidateRect(hwndNotify, &rNotifies, bErase);
        _TNSize(pTNPrivData);
        InvalidateRect(hwndNotify, &pTNPrivData->rNotifies, bErase);

        return(TRUE);
}

/*
 ** TrayNotifyCreate
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

HWND TrayNotifyCreate(HWND hwndParent, UINT uID, HINSTANCE hInst)
{
        WNDCLASSEX wc;

    wc.cbSize = SIZEOF(WNDCLASSEX);

        if (!GetClassInfoEx(hInst, c_szTrayNotify, &wc))
        {
                wc.lpszClassName = c_szTrayNotify;
                wc.style = CS_DBLCLKS;
                wc.lpfnWndProc = TrayNotifyWndProc;
                wc.hInstance = hInst;
                wc.hIcon = NULL;
                wc.hCursor = LoadCursor(NULL, IDC_ARROW);
                wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
                wc.lpszMenuName  = NULL;
                wc.cbClsExtra = 0;
                wc.cbWndExtra = SIZEOF(PTNPRIVDATA);
        wc.hIconSm = NULL;

                if (!RegisterClassEx(&wc))
                {
                        return(NULL);
                }

                if (!ClockCtl_Class(hInst))
                {
                        return(NULL);
                }

        }

        return(CreateWindowEx(WS_EX_STATICEDGE, c_szTrayNotify,
                NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0,
                hwndParent, (HMENU)uID, hInst, NULL));
}