#include "progman.h"
#include "ime.h"

BOOL FAR PASCAL IMEStringWindow(HWND,HANDLE);
int FAR PASCAL IMEWindowGetCnt(LPTSTR,LPTSTR);

#define HK_SHIFT    0x0100
#define HK_CONTROL  0x0200
#define HK_ALT      0x0400
#define HK_EXT      0x0800

#define F_EXT       0x01000000L

#define OBJ_ITEM                1


BOOL bNoScrollCalc = FALSE;
RECT rcArrangeRect;
HWND hwndScrolling = NULL;

BOOL APIENTRY InQuotes(LPTSTR sz);
void NEAR PASCAL ViewActiveItem(PGROUP pGroup);

/* Make the first item in a list the last and return a pointer to it.*/

PITEM PASCAL MakeFirstItemLast(PGROUP pGroup)
{
    PITEM pItemCur;

    /* Just quit if there's no list.*/

    if ((pItemCur = pGroup->pItems) == NULL)
        return NULL;

    /* Find the end of the list.*/
    for( ; pItemCur->pNext ; pItemCur = pItemCur->pNext)
    ;

    /* End of the list.*/
    /* This works even if there is only one item in the */
    /* list, it's a waste of time though.*/
    pItemCur->pNext = pGroup->pItems;
    pGroup->pItems = pGroup->pItems->pNext;
    pItemCur->pNext->pNext = NULL;

    return pItemCur->pNext;
}


VOID PASCAL GetItemText(PGROUP pGroup, PITEM pItem, LPTSTR lpT, int index)
{
    LPITEMDEF lpid;
    LPGROUPDEF lpgd;

    lpid = LockItem(pGroup,pItem);
    if (!lpid) {
        UnlockGroup(pGroup->hwnd);
        *lpT = 0;
        return;
    }

    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

    switch (index) {

    case 0:
        lstrcpy(lpT, (LPTSTR) PTR(lpgd, lpid->pName));
        break;

    case 1:
        lstrcpy(lpT, (LPTSTR) PTR(lpgd, lpid->pCommand));
        break;

    case 2:
        lstrcpy(lpT, (LPTSTR) PTR(lpgd, lpid->pIconPath));
        break;

    default:
        *lpT = 0;
        break;
    }

    GlobalUnlock(pGroup->hGroup);

    UnlockGroup(pGroup->hwnd);
}


ULONG Color16Palette[] = {
    0x00000000, 0x00800000, 0x00008000, 0x00808000,
    0x00000080, 0x00800080, 0x00008080, 0x00808080,
    0x00c0c0c0, 0x00ff0000, 0x0000ff00, 0x00ffff00,
    0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff
};

ULONG Color256Palette[] = {
    0x00000000, 0x00800000, 0x00008000, 0x00808000, 0x00000080, 0x00800080, 0x00008080, 0x00c0c0c0,
    0x00c0dcc0, 0x00a6caf0, 0x00cccccc, 0x00580800, 0x00600800, 0x00680800, 0x00700800, 0x00780800,
    0x00801000, 0x00881000, 0x00901000, 0x00981000, 0x00a01000, 0x00a81000, 0x00b01000, 0x00b81000,
    0x00c01800, 0x00c81800, 0x00d01800, 0x00d81800, 0x00e01800, 0x00e81800, 0x00f01800, 0x00f81800,
    0x00002000, 0x00082000, 0x00102000, 0x00182000, 0x00202000, 0x00282000, 0x00302000, 0x00382000,
    0x00402800, 0x00482800, 0x00502800, 0x00582800, 0x00602800, 0x00682800, 0x00702800, 0x00782800,
    0x00803000, 0x00883000, 0x00903000, 0x00983000, 0x00a03000, 0x00a83000, 0x00b03000, 0x00b83000,
    0x00c03800, 0x00c83800, 0x00d03800, 0x00d83800, 0x00e03800, 0x00e83800, 0x00f03800, 0x00f83800,
    0x00004010, 0x00084010, 0x00104010, 0x00184010, 0x00204010, 0x00284010, 0x00304010, 0x00384010,
    0x00404810, 0x00484810, 0x00504810, 0x00584810, 0x00604810, 0x00684810, 0x00704810, 0x00784810,
    0x00805010, 0x00885010, 0x00905010, 0x00985010, 0x00a05010, 0x00a85010, 0x00b05010, 0x00b85010,
    0x00c05810, 0x00c85810, 0x00d05810, 0x00d85810, 0x00e05810, 0x00e85810, 0x00f05810, 0x00f85810,
    0x00006010, 0x00086010, 0x00106010, 0x00186010, 0x00206010, 0x00286010, 0x00306010, 0x00386010,
    0x00406810, 0x00486810, 0x00506810, 0x00586810, 0x00606810, 0x00686810, 0x00706810, 0x00786810,
    0x00807010, 0x00887010, 0x00907010, 0x00987010, 0x00a07010, 0x00a87010, 0x00b07010, 0x00b87010,
    0x00c07810, 0x00c87810, 0x00d07810, 0x00d87810, 0x00e07810, 0x00e87810, 0x00f07810, 0x00f87810,
    0x00008020, 0x00088020, 0x00108020, 0x00188020, 0x00208020, 0x00288020, 0x00308020, 0x00388020,
    0x00408820, 0x00488820, 0x00508820, 0x00588820, 0x00608820, 0x00688820, 0x00708820, 0x00788820,
    0x00809020, 0x00889020, 0x00909020, 0x00989020, 0x00a09020, 0x00a89020, 0x00b09020, 0x00b89020,
    0x00c09820, 0x00c89820, 0x00d09820, 0x00d89820, 0x00e09820, 0x00e89820, 0x00f09820, 0x00f89820,
    0x0000a020, 0x0008a020, 0x0010a020, 0x0018a020, 0x0020a020, 0x0028a020, 0x0030a020, 0x0038a020,
    0x0040a820, 0x0048a820, 0x0050a820, 0x0058a820, 0x0060a820, 0x0068a820, 0x0070a820, 0x0078a820,
    0x0080b020, 0x0088b020, 0x0090b020, 0x0098b020, 0x00a0b020, 0x00a8b020, 0x00b0b020, 0x00b8b020,
    0x00c0b820, 0x00b820c8, 0x00b820d0, 0x00b820d8, 0x00b820e0, 0x00b820e8, 0x00b820f0, 0x00b820f8,
    0x0000c030, 0x00c03008, 0x00c03010, 0x00c03018, 0x00c03020, 0x00c03028, 0x00c03030, 0x00c03038,
    0x0040c830, 0x00c83048, 0x00c83050, 0x00c83058, 0x00c83060, 0x00c83068, 0x00c83070, 0x00c83078,
    0x0080d030, 0x00d03088, 0x00d03090, 0x00d03098, 0x00d030a0, 0x00d030a8, 0x00d030b0, 0x00d030b8,
    0x00c0d830, 0x00c8d830, 0x00d0d830, 0x00d8d830, 0x00e0d830, 0x00e8d830, 0x00f0d830, 0x00f8d830,
    0x0000e030, 0x0008e030, 0x0010e030, 0x0018e030, 0x0020e030, 0x0028e030, 0x0030e030, 0x0038e030,
    0x0040e830, 0x0048e830, 0x0050e830, 0x0058e830, 0x0060e830, 0x0068e830, 0x0070e830, 0x0078e830,
    0x0080f030, 0x0088f030, 0x0090f030, 0x0098f030, 0x00a0f030, 0x00a8f030, 0x00fffbf0, 0x00a0a0a4,
    0x00808080, 0x00ff0000, 0x0000ff00, 0x00ffff00, 0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff
};


HICON APIENTRY GetItemIcon(HWND hwnd, PITEM pitem)
{

    LPGROUPDEF lpgd;
    LPITEMDEF lpid;
    HICON hIcon = NULL;
    DWORD dwVer;
    HANDLE h;
    LPBYTE p;
    INT id = 0;
    INT cb;
    DWORD colors, size;
    PBITMAPINFOHEADER pbih, pbihNew;
    LPVOID palette;

    lpgd = LockGroup(hwnd);
    if (!lpgd)
        return DuplicateIcon(hAppInstance, hItemIcon);
        //return hItemIcon;
    lpid = ITEM(lpgd,pitem->iItem);

    if ((SHORT)lpid->cbIconRes > 0) {
        if (lpid->wIconVer == 2)
            dwVer = 0x00020000;
        else
            dwVer = 0x00030000;

        pbihNew = NULL;
        pbih = (PBITMAPINFOHEADER)PTR(lpgd, lpid->pIconRes);
        size = lpid->cbIconRes;

        if (pbih->biClrUsed == -1) {
            colors = (1 << (pbih->biPlanes * pbih->biBitCount));
            size += colors * sizeof(RGBQUAD);

            if (colors == 16) {
                palette = Color16Palette;
            } else if (colors == 256) {
                palette = Color256Palette;
            } else {
                palette = NULL;
            }

            if (palette != NULL)
                pbihNew = (PBITMAPINFOHEADER)LocalAlloc(LPTR, size);

            if (pbihNew != NULL) {
                RtlCopyMemory(pbihNew, pbih, sizeof( *pbih ));
                pbihNew->biClrUsed = 0;
                RtlCopyMemory((pbihNew+1), palette, colors * sizeof(RGBQUAD));
                RtlCopyMemory((PCHAR)(pbihNew+1) + (colors * sizeof(RGBQUAD)),
                              (pbih+1),
                              lpid->cbIconRes - sizeof(*pbih)
                             );

                pbih = pbihNew;
            }
            else {
                //
                // reset size
                //
                size = lpid->cbIconRes;
            }
        }

        hIcon = CreateIconFromResource((PBYTE)pbih, size, TRUE, dwVer);
        if (pbihNew != NULL)
            LocalFree(pbihNew);
    }

    if (!hIcon) {
      if (h = FindResource(hAppInstance, (LPTSTR) MAKEINTRESOURCE(ITEMICON), RT_GROUP_ICON)) {
          h = LoadResource(hAppInstance, h);
          p = LockResource(h);
          id = LookupIconIdFromDirectory(p, TRUE);
          UnlockResource(h);
          FreeResource(h);
      }
      if (h = FindResource(hAppInstance, (LPTSTR)  MAKEINTRESOURCE(id), (LPTSTR) MAKEINTRESOURCE(RT_ICON))) {
          cb = SizeofResource(hAppInstance, h);
          h = LoadResource(hAppInstance, h);
          p = LockResource(h);
          hIcon = CreateIconFromResource(p, cb, TRUE, 0x00030000);
          UnlockResource(h);
          FreeResource(h);
      }

    }
    UnlockGroup(hwnd);
    return hIcon;
}


COLORREF PASCAL GetTitleTextColor(VOID)
{
    COLORREF color;

    color = GetSysColor(COLOR_WINDOW);
    if (((WORD)LOBYTE(LOWORD(color)) +
             (WORD)HIBYTE(LOWORD(color)) +
         (WORD)LOBYTE(HIWORD(color))) >= 3*127)
      {
        return RGB(0,0,0);
      }
    else
      {
        return RGB(255,255,255);
      }
}

#define REVERSEPAINT

/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/


void NEAR PASCAL ReverseGroupList(PGROUP pGroup)
{
    PITEM pitem, p1, p2;

    for (p1 = pGroup->pItems, p2 = p1->pNext, p1->pNext = NULL; p2; ) {
	    pitem = p2->pNext;
	    p2->pNext = p1;
	    p1 = p2;
	    p2 = pitem;
	}
    pGroup->pItems = p1;
}

VOID PASCAL PaintGroup(HWND hwnd)
{
    register HDC hdc;
    PGROUP pGroup;
    PITEM pitem;
    PAINTSTRUCT ps;
    LPGROUPDEF lpgd;
    LPITEMDEF lpid;
    int fontheight;
    HBRUSH hbrTitle;
    HFONT hFontT;
    RECT rcWin;
    TEXTMETRIC tm;

    HDC hdcMem;
    HBITMAP hbmTemp;
    HBRUSH hbr, hbrTemp;
    INT nMax;
    HICON hIcon;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);

    hdc = BeginPaint(hwnd, &ps);
    if (!pGroup->pItems) {
        goto Exit;
    }

    GetClientRect(hwnd, &rcWin);
#ifdef WEIRDBUG
/* DavidPe - 05/15/91
 *  For some reason RectVisible() will return FALSE in
 *  situations it shouldn't.  Since this is only a
 *  performance optimization, we can ignore the problem
 *  for now.
 */
    if (!RectVisible(hdc, &rcWin))
        goto Exit;
#endif

    if (!(lpgd = LockGroup(hwnd)))
        goto Exit;

    hFontT = SelectObject(hdc, hFontTitle);

    GetTextMetrics(hdc, &tm);

    // ToddB: This seems like a good point, I don't see why tmExternalLeading should ever
    //      need to be considered.  The result of decreasing the FontHeight is that DrawText
    //      will be used instead of TextOut in some cases, which should be harmless but might
    //      effect the apearence of some single line icon titles.

//#ifdef JAPAN
    // Why we should think about ExternalLeading though we calculate the
    // title rectange by DrawText() without DT_EXTERNALLEADING. -YN
    fontheight = tm.tmHeight;
//#else
//    fontheight = tm.tmHeight + tm.tmExternalLeading;
//#endif

    SetBkMode(hdc, TRANSPARENT);
    SetTextColor(hdc, GetTitleTextColor());
    hbrTitle = NULL;

    hdcMem = CreateCompatibleDC(hdc);
    if (!hdcMem)
        goto BitmapSetupComplete;

    if (pGroup->hbm) {
        hbmTemp = SelectObject(hdcMem, pGroup->hbm);
        if (hbmTemp)
            goto BitmapSetupComplete;
        else
            DeleteObject(pGroup->hbm);
    }

    for (nMax = 1, pitem = pGroup->pItems; pitem; pitem = pitem->pNext) {
	    if (nMax <= pitem->iItem)
	        nMax = pitem->iItem + 1;
    }

    pGroup->hbm = CreateDiscardableBitmap(hdc, cxIcon*lpgd->cItems, cyIcon);
    if (!pGroup->hbm)
        goto NukeMemDC;

    hbmTemp = SelectObject(hdcMem, pGroup->hbm);
    if (!hbmTemp)
        goto NukeBitmap;

    hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    if (!hbr)
        goto NukeBitmap;

    hbrTemp = SelectObject(hdcMem, hbr);
    if (!hbrTemp)
        goto NukeBrush;

    PatBlt(hdcMem, 0, 0, cxIcon * lpgd->cItems, cyIcon, PATCOPY);
    SelectObject(hdcMem, hbrTemp);
    DeleteObject(hbr);

    for (pitem = pGroup->pItems; pitem != NULL; pitem = pitem->pNext) {
        if (hIcon = GetItemIcon(hwnd, pitem)) {
            DrawIcon(hdcMem, pitem->iItem * cxIcon, 0, hIcon);
            DestroyIcon(hIcon);
        } else {
            goto DeselectAndNukeBitmap;
        }
    }

    goto BitmapSetupComplete;

NukeBrush:
     DeleteObject(hbr);

DeselectAndNukeBitmap:
    SelectObject(hdcMem, hbmTemp);

NukeBitmap:
     DeleteObject(pGroup->hbm);
     pGroup->hbm = NULL;

NukeMemDC:
     DeleteDC(hdcMem);
     hdcMem = NULL;

BitmapSetupComplete:

    ReverseGroupList(pGroup); // reverse the icon list

    /* Paint the icons */
    for (pitem = pGroup->pItems; pitem; pitem = pitem->pNext) {
      	if (!pitem->pNext
    	      && pGroup == pCurrentGroup
	          && hwndProgman == GetActiveWindow()) {
	        hbrTitle = (HANDLE)1;       // Use it as a flag
    	}
    	else
    	    hbrTitle = (HANDLE)0;

    	lpid = ITEM(lpgd,pitem->iItem);

	    if (!bMove || !hbrTitle) {
	        if (hdcMem) {
        		BitBlt(hdc, pitem->rcIcon.left + cxOffset,
		               pitem->rcIcon.top + cyOffset,
        		       lpgd->cxIcon, lpgd->cyIcon, hdcMem,
		               lpgd->cxIcon*pitem->iItem, 0,
        		       SRCCOPY);
	        }
            else {
	    	    if (RectVisible(hdc,&pitem->rcIcon)) {
		            if (hIcon = GetItemIcon(hwnd,pitem)) {
		            	DrawIcon(hdc, pitem->rcIcon.left + cxOffset,
                				 pitem->rcIcon.top + cyOffset, hIcon);
        		    }
                    else {
		            	PatBlt(hdc,pitem->rcIcon.left + cxOffset,
            			       pitem->rcIcon.top + cyOffset,
			                   cxIcon, cyIcon, BLACKNESS);
        		    }
		        }
    	    }
	    }
    }

    /* Paint the titles. */
    for (pitem = pGroup->pItems; pitem; pitem = pitem->pNext) {
        /* test for the active icon */

      	if (!pitem->pNext
    	      && pGroup == pCurrentGroup
	          && hwndProgman == GetActiveWindow()) {
    	    SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT));
	        hbrTitle = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION));
    	}
        else {
	        hbrTitle = (HANDLE)0;
    	}

	    lpid = ITEM(lpgd,pitem->iItem);

    	if (!bMove || !hbrTitle) {
	        if (hbrTitle)
        		FillRect(hdc, &(pitem->rcTitle), hbrTitle);

	        /* draw multi line titles like USER does */

    	    if (pitem->rcTitle.bottom - pitem->rcTitle.top < fontheight*2)
                TextOut(hdc, pitem->rcTitle.left+cxOffset, pitem->rcTitle.top,
                        (LPTSTR) PTR(lpgd, lpid->pName), lstrlen((LPTSTR) PTR(lpgd, lpid->pName)));
            else {
                if (RectVisible(hdc,&pitem->rcTitle)) {
	            DrawText(hdc,
                             (LPTSTR)PTR(lpgd, lpid->pName), -1,
                             &(pitem->rcTitle),
                       	     bIconTitleWrap ?
                    	        DT_CENTER | DT_WORDBREAK | DT_NOPREFIX :
                                DT_CENTER | DT_WORDBREAK | DT_NOPREFIX | DT_SINGLELINE);

                }
    	    }
        }


        if (hbrTitle) {
            SetTextColor(hdc, GetTitleTextColor());
            DeleteObject(hbrTitle);
            hbrTitle = NULL;
        }
    }

    ReverseGroupList(pGroup);	// re-reverse the icon list

    if (hFontT) {
        SelectObject(hdc, hFontT);
    }

    if (hdcMem) {
        SelectObject(hdcMem, hbmTemp);
        DeleteDC(hdcMem);
    }

    UnlockGroup(hwnd);

Exit:
    SetBkMode(hdc, OPAQUE);
    EndPaint(hwnd, &ps);

#ifdef DEBUG
    ProfStop();
    {
    TCHAR buf[80];
    wsprintf(buf, TEXT("msec to paint group = %ld\r\n"), GetTickCount() - dwStartTime);
    OutputDebugString(buf);
    }
#endif
}

/*-------------------------------------------------------------------------*/
/*                                                                         */
/* Draw the group icon.                                                    */
/*                                                                         */
/*-------------------------------------------------------------------------*/
VOID DrawGroupIcon(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hDC;
    PGROUP pGroup;

    hDC = BeginPaint(hwnd, &ps);
    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);
    if (pGroup->fCommon) {
        DrawIcon(hDC, cxOffset, cyOffset, hCommonGrpIcon);
    }
    else {
        DrawIcon(hDC, cxOffset, cyOffset, hGroupIcon);
    }
    EndPaint(hwnd, &ps);
}


PITEM PASCAL ItemHitTest(
    PGROUP pGroup,
    POINTS pts)
{
    PITEM pItem;
    POINT pt;

    pt.x = (int)pts.x;
    pt.y = (int)pts.y;

    for (pItem = pGroup->pItems; pItem; pItem = pItem->pNext) {
        if (PtInRect(&pItem->rcIcon, pt) || PtInRect(&pItem->rcTitle, pt)) {
            break;
        }
    }

    return pItem;
}


HRGN PASCAL IconExcludedRgn(PGROUP pGroup, PITEM pItem)
{
    RECT rc;
    PITEM pItemT;
    HRGN hrgn, hrgnT;

    hrgn = CreateRectRgn(0,0,0,0);

    if (!hrgn)
        return NULL;

    hrgnT = CreateRectRgn(0,0,0,0);
    if (!hrgnT)
      {
        return hrgn;
      }

    for (pItemT = pGroup->pItems;
         pItemT && pItemT != pItem;
         pItemT = pItemT->pNext)
      {
        if (IntersectRect(&rc,&pItem->rcIcon,&pItemT->rcIcon))
          {
            SetRectRgn(hrgnT,rc.left,rc.top,rc.right,rc.bottom);
            CombineRgn(hrgn,hrgn,hrgnT,RGN_OR);
          }
        if (IntersectRect(&rc,&pItem->rcIcon,&pItemT->rcTitle))
          {
            SetRectRgn(hrgnT,rc.left,rc.top,rc.right,rc.bottom);
            CombineRgn(hrgn,hrgn,hrgnT,RGN_OR);
          }
      }

    DeleteObject(hrgnT);

    return hrgn;
}

VOID APIENTRY InvalidateIcon(PGROUP pGroup, PITEM pItem)
{
    RECT rc;

    if (!pGroup || !pItem)
        return;
    UnionRect(&rc, &pItem->rcIcon, &pItem->rcTitle);
    if (bAutoArranging)
        UnionRect(&rcArrangeRect, &rcArrangeRect, &rc);
    else
        InvalidateRect(pGroup->hwnd,&rc,TRUE);
}

VOID PASCAL BringItemToTop(PGROUP pGroup, PITEM pItem, BOOL fUpdate)
{
    PITEM pItemT;
    HRGN hrgn;

    if (pItem == pGroup->pItems) {
        return;
    }

    if (hrgn = IconExcludedRgn(pGroup, pItem)) {
        InvalidateRgn(pGroup->hwnd, hrgn, TRUE);
        DeleteObject(hrgn);
    }

    /*
     * At this point we know there is at least two items, and we're not the
     * first one...
     */

    for (pItemT = pGroup->pItems; pItemT->pNext != pItem; pItemT = pItemT->pNext)
        ;

    pItemT->pNext = pItem->pNext;
    pItem->pNext = pGroup->pItems;
    pGroup->pItems = pItem;

    /*
     * Invalidate the whole titles in order to change the color.
     */
    if (fUpdate) {
        InvalidateRect(pGroup->hwnd, &pItem->rcTitle, TRUE);
        InvalidateRect(pGroup->hwnd, &pItem->pNext->rcTitle, TRUE);
    }
}

VOID PASCAL ClickOn(HWND hwnd, POINTS pts)
{
    PGROUP pGroup;
    PITEM pItem;
    POINT pt;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);

    pItem = ItemHitTest(pGroup, pts);
    if (!pItem) {
        return;
    }

    BringItemToTop(pGroup, pItem, TRUE);
    ViewActiveItem(pGroup);

    pt.x = (int)pts.x;
    pt.y = (int)pts.y;

    *(LPPOINT)&rcDrag.left = pt;
    *(LPPOINT)&rcDrag.right = pt;
    hwndDrag = hwnd;

    InflateRect(&rcDrag, GetSystemMetrics(SM_CXDOUBLECLK) / 2,
            GetSystemMetrics(SM_CYDOUBLECLK) / 2);
}


VOID PASCAL DragItem(HWND hwnd)
{
    PGROUP pGroup;
    PITEM pItem;
    HICON hIcon;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);
    pItem = pGroup->pItems;

    if (!pItem || hwndDrag != hwnd)
        goto ProcExit;

    /* If the control key isn't down, do a Move operation. */
    bMove = !(GetKeyState(VK_CONTROL) & 0x8000);

    /* Don't allow "moves" from RO groups. */
    if (pGroup->fRO && bMove == TRUE)
        goto ProcExit;

    /*
     * Redraw the window minus the item we're moving.
     * REVIEW - if you just painted the background colour into the
     * pItem->rcIcon area then you could remove the bMove code from
     * PaintGroup().
     */
    if (bMove) {
        InvalidateIcon(pGroup,pItem);
        UpdateWindow(hwnd);
    }

    hIcon = GetItemIcon(hwnd,pItem);

    // BUG BUG  MAKELONG(pGroup,pItem) doesn't make sense since all
    // pointers all LOMG in WIN32. Will need to change the parameters!
    // johannec 08-19-91
    if (DragObject(hwndMDIClient, hwnd, (UINT)OBJ_ITEM,
                   MAKELONG(pGroup,pItem), hIcon) == DRAG_COPY) {
        if (bMove)
            DeleteItem(pGroup,pItem);
    }
    else {
        /* Drag was SWP or drag failed... just show the item. */
        if (bMove) {
            bMove = FALSE;
            InvalidateIcon(pGroup,pItem);
        }
    }
    DestroyIcon(hIcon);
ProcExit:
    bMove = FALSE;
}


void PASCAL GetRealClientRect(
    HWND   hwnd,
    DWORD  dwStyle,
    LPRECT lprcClient)
{
    DWORD Style;

    Style = GetWindowLong(hwnd, GWL_STYLE);

        /*BUG BUG will GWL_STYLE work???*/

    SetWindowLong(hwnd,GWL_STYLE,dwStyle);
    GetWindowRect(hwnd,lprcClient);
    ScreenToClient(hwnd,(LPPOINT)&lprcClient->left);
    ScreenToClient(hwnd,(LPPOINT)&lprcClient->right);
    SendMessage(hwnd,WM_NCCALCSIZE,0,(LPARAM)lprcClient);
}


VOID APIENTRY CalcGroupScrolls(HWND hwnd)
{
    register PGROUP pGroup;
    register PITEM pItem;
    RECT rcClient;
    RECT rcRange;
    RECT rcT;
    DWORD dwStyle, dwStyleNew, dwStyleT;
    int iMinPos, iMaxPos;

    if (bNoScrollCalc || IsIconic(hwnd))
        return;

    // Stop re-entrance of this routine.
    bNoScrollCalc = TRUE;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);

    if (!pGroup->pItems) {
        // No items...
        SetRectEmpty(&rcRange);
    	goto ChangeStyle;
    }

    hwndScrolling = hwnd;

    // If the user has selected auto arranging then make
    // the next item in the z-order visable.
    if (bAutoArrange)
        ViewActiveItem(pGroup);

    SetRectEmpty(&rcRange);

    for (pItem = pGroup->pItems; pItem; pItem = pItem->pNext)
      {
        UnionRect(&rcRange,&rcRange,&pItem->rcIcon);
    	rcT.top = pItem->rcTitle.top;		// don't include the
	    rcT.bottom = pItem->rcTitle.bottom;	// title overhang part
    	rcT.left = pItem->rcIcon.left;
	    rcT.right = pItem->rcIcon.right;
        UnionRect(&rcRange,&rcRange,&rcT);
      }

    if (rcRange.left != rcRange.right)
      {
        // Add on a bit for the left border here.
        rcRange.left -= ((cxArrange-cxIconSpace)/2)+cxOffset;
        // Don't add on a right border so we can cram as many icons in as poss.
        // rcRange.right += ((cxArrange-cxIconSpace)/2);

        // ADJUST THE RECT SO THAT WE DON'T GET SCROLL BARS IF ONLY THE BORDERS
        // OF TEXT ARE NOT VISIBLE ~~~
      }

ChangeStyle:

    dwStyleNew = dwStyle = GetWindowLong(hwnd,GWL_STYLE);

    dwStyleNew &= ~(WS_HSCROLL | WS_VSCROLL);

    for (;;)
      {
        dwStyleT = dwStyleNew;
        GetRealClientRect(hwnd, dwStyleNew, &rcClient);

        if (rcRange.left < rcClient.left || rcRange.right > rcClient.right)
            dwStyleNew |= WS_HSCROLL;
        if (rcRange.top < rcClient.top || rcRange.bottom > rcClient.bottom)
            dwStyleNew |= WS_VSCROLL;

        if (dwStyleNew == dwStyleT)
            break;
      }

    if (dwStyleNew == dwStyle && !(dwStyle & (WS_VSCROLL|WS_HSCROLL)))
      {
        /* none there and don't need to add 'em!
         */
        goto ProcExit;
      }

    UnionRect(&rcRange,&rcClient,&rcRange);

    /* union garantees that left==right or top==bottom in case of no
     * scrollbar.
     */
    rcRange.right -= rcClient.right-rcClient.left;
    rcRange.bottom -= rcClient.bottom-rcClient.top;

    /* if the style changed, don't redraw in sb code, just redraw the
     * frame at the end cause the whole ncarea has to be repainted
     * if it hasn't changed, just move the thumb
     */

    if (dwStyleNew==dwStyle)
      {
        if (dwStyleNew & WS_HSCROLL)
          {
            if (GetScrollPos(hwnd,SB_HORZ)!=0)
                goto SetScrollInfo;
            GetScrollRange(hwnd,SB_HORZ,&iMinPos,&iMaxPos);
            if ((iMinPos != rcRange.left) || (iMaxPos != rcRange.right))
                goto SetScrollInfo;
          }
        if (dwStyleNew & WS_VSCROLL)
          {
            if (GetScrollPos(hwnd,SB_VERT)!=0)
                goto SetScrollInfo;
            GetScrollRange(hwnd,SB_VERT,&iMinPos,&iMaxPos);
            if ((iMinPos != rcRange.top) || (iMaxPos != rcRange.bottom))
                goto SetScrollInfo;
          }
        goto ProcExit;
      }

SetScrollInfo:
    SetScrollPos(hwnd,SB_HORZ,0,FALSE);
    SetScrollPos(hwnd,SB_VERT,0,FALSE);
    SetScrollRange(hwnd,SB_HORZ,rcRange.left,rcRange.right,FALSE);
    SetScrollRange(hwnd,SB_VERT,rcRange.top,rcRange.bottom,FALSE);

    SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE
                                         | SWP_NOMOVE | SWP_NOACTIVATE
                                         | SWP_DRAWFRAME);
ProcExit:
    // Allow other scroll calculations.
    bNoScrollCalc = FALSE;
}


VOID PASCAL ScrollGroup(PGROUP pGroup, int xMove, int yMove, BOOL fCalc)
{
    register PITEM pItem;

    for (pItem = pGroup->pItems; pItem; pItem = pItem->pNext)
      {
        OffsetRect(&pItem->rcIcon,xMove,yMove);
        OffsetRect(&pItem->rcTitle,xMove,yMove);
      }
    ScrollWindow(pGroup->hwnd,xMove,yMove,NULL,NULL);

    UpdateWindow(pGroup->hwnd);

    if (fCalc)
        CalcGroupScrolls(pGroup->hwnd);
}

VOID APIENTRY ViewActiveItem(PGROUP pGroup)
{
    RECT rcClient, rc;
    int xMove = 0, yMove = 0;

    GetClientRect(pGroup->hwnd,&rcClient);

    UnionRect(&rc, &pGroup->pItems->rcIcon, &pGroup->pItems->rcTitle);
    // Clip width to that of icon i.e. ignore width of text.
    rc.left = pGroup->pItems->rcIcon.left;
    rc.right = pGroup->pItems->rcIcon.right;


    if (rc.left < rcClient.left)
        xMove = rcClient.left - rc.left;
    else if (rc.right > rcClient.right)
        xMove = rcClient.right - rc.right;

    if (rc.top < rcClient.top)
        yMove = rcClient.top - rc.top;
    else if (rc.bottom > rcClient.bottom)
        yMove = rcClient.bottom - rc.bottom;

    if (xMove || yMove)
        ScrollGroup(pGroup, xMove, yMove,TRUE);
}


BOOL FAR PASCAL CheckHotKey(WPARAM wParam, LPARAM lParam)
{
    HWND hwndT;
    PGROUP pGroup;
    PITEM pItem;

    switch (wParam)
    {
    case VK_SHIFT:
    case VK_CONTROL:
    case VK_MENU:
    case VK_RETURN:
        return FALSE;
    }

    if (GetKeyState(VK_SHIFT) < 0) {
    	// DBG((" + SHIFT"));
	    wParam |= HK_SHIFT;
    }
    if (GetKeyState(VK_CONTROL) < 0) {
    	// DBG((" + CONTROL"));
	    wParam |= HK_CONTROL;
    }
    if (GetKeyState(VK_MENU) < 0) {
    	// DBG((" + ALT"));
	    wParam |= HK_ALT;
    }
    if (lParam & F_EXT) {
    	// DBG((" EXTENDED"));
	    wParam |= HK_EXT;
    }

    // DBG(("... Full code %4.4X...\r\n",wParam));

    for (hwndT = GetWindow(hwndMDIClient,GW_CHILD);
          hwndT;
          hwndT = GetWindow(hwndT,GW_HWNDNEXT)) {
        if (GetWindow(hwndT,GW_OWNER))
            continue;

        pGroup = (PGROUP)GetWindowLongPtr(hwndT,GWLP_PGROUP);

        for (pItem = pGroup->pItems; pItem; pItem = pItem->pNext) {
    	    // DBG(("Checking (%4.4X,%4.4X)...\r\n",pGroup,pItem));
            if (GroupFlag(pGroup,pItem,(WORD)ID_HOTKEY) == (WORD)wParam) {
	        	// DBG(("F*O*U*N*D\r\n"));
                ExecItem(pGroup,pItem,FALSE,FALSE);
                return TRUE;
            }
        }
    }

    return FALSE;
}


VOID APIENTRY KeyWindow(HWND hwnd, WORD wDirection)
{
    int     wT;
    int     wNext;
    RECT    rc;
    RECT    rcT;
    POINT   ptA;
    POINT   ptT;
    PGROUP  pGroup;
    PITEM   pItem, pItemNext;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);

    if (!pGroup->pItems)
        return;

    wNext = 0x7FFF;
    pItemNext = NULL;
    CopyRect(&rc,&pGroup->pItems->rcIcon);

    for (pItem = pGroup->pItems->pNext; pItem; pItem = pItem->pNext) {
        CopyRect(&rcT,&pItem->rcIcon);
        ptT.x = rcT.left - rc.left;
        ptT.y = rcT.top - rc.top;
        ptA.x = (ptT.x < 0) ? -ptT.x : ptT.x;
        ptA.y = (ptT.y < 0) ? -ptT.y : ptT.y;

        switch (wDirection) {
            case VK_LEFT:
                if ((ptT.x >= 0) || (ptA.x < ptA.y))
                    continue;
                break;

            case VK_RIGHT:
                if ((ptT.x <= 0) || (ptA.x < ptA.y))
                    continue;
                break;

            case VK_DOWN:
                if ((ptT.y <= 0) || (ptA.y < ptA.x))
                    continue;
                break;

            case VK_UP:
                if ((ptT.y >= 0) || (ptA.y < ptA.x))
                    continue;
                break;

            default:
                /* illegal key
                 */
                return;
        }

        wT = ptA.y + ptA.x;

        if (wT <= wNext) {
            wNext = wT;
            pItemNext = pItem;
        }
    }

    if (pItemNext) {
        BringItemToTop(pGroup,pItemNext, TRUE);
        ViewActiveItem(pGroup);
    }
}


VOID APIENTRY CharWindow(register HWND hwnd, register WORD wChar)
{
    LPGROUPDEF lpgd;
    LPITEMDEF lpid;
    PGROUP pGroup;
    PITEM pItem, pItemLast;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);

    if (!pGroup->pItems)
        return;

    lpgd = LockGroup(hwnd);
    if (!lpgd)
        return;

    /* Search for item, skip the currently selected one.*/
    for ( pItem = pGroup->pItems->pNext; pItem; pItem=pItem->pNext)
      {
        lpid = ITEM(lpgd,pItem->iItem);
        if (CharUpper((LPTSTR)(DWORD_PTR)wChar)
          == CharUpper((LPTSTR)(DWORD_PTR)(BYTE)*PTR(lpgd, lpid->pName)))
          {
            pItemLast = MakeFirstItemLast(pGroup);
            BringItemToTop(pGroup,pItem, FALSE);
            /* Handle updates.*/
            InvalidateRect(pGroup->hwnd,&pItem->rcTitle,TRUE);
            InvalidateRect(pGroup->hwnd,&pItemLast->rcTitle,TRUE);
            ViewActiveItem(pGroup);
            break;
          }
      }
    UnlockGroup(hwnd);
}


VOID APIENTRY ScrollMessage(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    int           wMin;
    int           wMax;
    int           wPos;
    int           wInc;
    int           wPage;
    int           wNewPos;
    RECT          rcClient;
    int           yMove;
    int           xMove;
    BOOL          fTemp;

    GetClientRect(hwnd, &rcClient);

    if (uiMsg == WM_HSCROLL) {
        GetScrollRange(hwnd, SB_HORZ, &wMin, &wMax);
        wPos = GetScrollPos(hwnd, SB_HORZ);
        wInc = cxIconSpace + cxArrange / 2;
        wPage = rcClient.right-rcClient.left;
    }
    else {
        GetScrollRange(hwnd, SB_VERT, &wMin, &wMax);
        wPos = GetScrollPos(hwnd, SB_VERT);
        wInc = cyArrange;
        wPage = rcClient.bottom-rcClient.top;
    }

    switch (GET_WM_VSCROLL_CODE(wParam, lParam)) {
        case SB_BOTTOM:
            wNewPos = wMax;
            break;

        case SB_TOP:
            wNewPos = wMin;
            break;

        case SB_LINEDOWN:
            wNewPos = wPos + wInc;
            break;

        case SB_LINEUP:
            wNewPos = wPos - wInc;
            break;

        case SB_PAGEDOWN:
            wNewPos = wPos + wPage;
            break;

        case SB_PAGEUP:
            wNewPos = wPos - wPage;
            break;

        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:
            wNewPos = (INT)(SHORT)GET_WM_VSCROLL_POS(wParam, lParam);
            break;

        case SB_ENDSCROLL:
            // We might suddenly not need the scroll bars anymore so
            // check now.
            // Stop CGS from moving the view.
            fTemp = bAutoArrange;
            bAutoArrange = FALSE;
    	    CalcGroupScrolls(hwnd);
            bAutoArrange = fTemp;

            /*** FALL THRU ***/

        default:
            return;
    }

    if (wNewPos < wMin)
        wNewPos = wMin;
    else if (wNewPos > wMax)
        wNewPos = wMax;

    if (uiMsg == WM_VSCROLL) {
        SetScrollPos(hwnd, SB_VERT, wNewPos, TRUE);
        yMove = wPos - wNewPos;
        xMove = 0;
    }
    else {
        SetScrollPos(hwnd, SB_HORZ, wNewPos, TRUE);
        yMove = 0;
        xMove = wPos - wNewPos;
    }

    ScrollGroup((PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP),xMove,yMove,FALSE);
}


VOID PASCAL OfficialRect(
    LPRECT lprc,
    int x,
    int y,
    int xOffset,
    int yOffset)
{
    // Work out were the icon should go in the icon grid, taking
    // note of where the scroll bars are.

    lprc->right = (lprc->left = x-xOffset + (cxIconSpace - cxArrange) / 2) +
            cxArrange - 1;
    lprc->bottom = (lprc->top = y-yOffset) + cyArrange - 1;
}


BOOL PASCAL IconOverlaps(
    PGROUP pGroup,
    LPRECT lprc,
    int xOffset,
    int yOffset)
{
    PITEM pItem;
    RECT rcT;

    for (pItem = pGroup->pItems; pItem; pItem = pItem->pNext) {
        // Ignore icons at -1. This is where icon's get put when
        // we don't know where to put them and they will get moved
        // later.
        if (pItem->rcIcon.left == -1) {
            continue;
        }

        OfficialRect(&rcT, pItem->rcIcon.left, pItem->rcIcon.top, xOffset, yOffset);
        if (IntersectRect(&rcT, &rcT, lprc)) {
            return TRUE;
        }
    }
    return FALSE;
}


/*
 * NB This is called for every icon at init time so put anything to do with
 * finding icon positions inside the `if' because that's skipped on init.
 * If you don't it'll get tres slow.
 */

VOID PASCAL ComputeIconPosition(
    PGROUP pGroup,
    POINT pt,
    LPRECT lprcIcon,
    LPRECT lprcTitle,
    LPTSTR lpText)
{
    HDC hdc;
    int cch;
    RECT rcClient, rcT;
    HFONT hFontT;
    int xsp, ysp;       // Current position of scrollbar.
    int vMax, vMin;     // Range.
    int hMax, hMin;     // Range.
    int xOffset, yOffset;
    DWORD dwStyle;

    if (pt.x == -1) {
        /*
         * Icon is in "find me a default position" mode...
         * so search the icon space for it...
         */
        // Get the current window style.
        dwStyle = GetWindowLong(pGroup->hwnd,GWL_STYLE);

        if (dwStyle & WS_MINIMIZE) {
            // DBG(("PM.CIP: Window Minimised\n\r"));
            // We want to use the restored state of the window.
            GetInternalWindowPos(pGroup->hwnd, &rcClient, NULL);
            // Convert from screen coords to client coords.
            OffsetRect(&rcClient, -rcClient.left, -rcClient.top);
        }
        else {
            // DBG(("PM.CIP: Window normal or maxed.\n\r"));
            // Take into account scroll bars.
            GetClientRect(pGroup->hwnd, &rcClient);
        }

        if (dwStyle & WS_HSCROLL) {
             xsp = GetScrollPos(pGroup->hwnd, SB_HORZ);
             GetScrollRange(pGroup->hwnd, SB_HORZ, &hMin, &hMax);
             xOffset = xsp-hMin;     // Offset icon grid to match scroll bar pos.
        }
        else {
             xOffset = 0;
        }

        if (dwStyle & WS_VSCROLL) {
             ysp = GetScrollPos(pGroup->hwnd, SB_VERT);
             GetScrollRange(pGroup->hwnd, SB_VERT, &vMin, &vMax);
             yOffset = ysp-vMin;     // Offset icon grid.
        }
        else {
             yOffset = 0;
        }

        pt.x = (cxArrange - cxIconSpace) / 2 + cxOffset - xOffset;
        pt.y = 0 - yOffset;
        /* Set this icon's left to -1 so that it'll be excluded
         * by the IconOverlaps check.
         */
        lprcIcon->left = -1;

        for (;;) {
            OfficialRect(&rcT, pt.x, pt.y, xOffset, yOffset);

            if (!IconOverlaps(pGroup, &rcT, xOffset, yOffset)) {
                break;
            }

            if (rcT.right + cxArrange > rcClient.right) {
                pt.x = (cxArrange-cxIconSpace)/2 + cxOffset - xOffset;
                pt.y += cyArrange;
            }
            else {
                pt.x += cxArrange;
            }
        }
    }

    SetRect(lprcIcon, pt.x, pt.y, pt.x+cxIconSpace, pt.y+cyIconSpace);

    if (IsRectEmpty(lprcTitle)) {
        cch = lstrlen(lpText);

        hdc = GetDC(pGroup->hwnd);
        hFontT = SelectObject(hdc, hFontTitle);

        /*
         * Compute the icon rect using DrawText.
         */
        lprcTitle->right = cxArrange - (2 * cxOffset);
        DrawText(hdc, lpText, -1, lprcTitle, bIconTitleWrap ?
            (WORD)(DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX) :
            (WORD)(DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX | DT_SINGLELINE));

        if (hFontT) {
            SelectObject(hdc, hFontT);
        }
        ReleaseDC(pGroup->hwnd, hdc);
        lprcTitle->right += cxOffset * 2;
        lprcTitle->bottom += dyBorder * 2;

    }
    else {
        SetRect(lprcTitle, 0, 0, lprcTitle->right - lprcTitle->left,
                lprcTitle->bottom - lprcTitle->top);
    }


    OffsetRect(lprcTitle, pt.x+(cxIconSpace/2)-((lprcTitle->right-lprcTitle->left)/2),
                  pt.y + cyIconSpace - dyBorder);

// REVIEW Very expensive to do this here.
//    if ((bAutoArrange) && (!bAutoArranging))
//	      ArrangeItems(pGroup->hwnd);
}

VOID APIENTRY MoveItem(PGROUP pGroup, PITEM pItem, POINT pt)
{
    LPITEMDEF lpid;
    LPGROUPDEF lpgd;

    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

    /*
     * If the position is the same, ignore
     */
    if ((pt.x == pItem->rcIcon.left) && (pt.y == pItem->rcIcon.top)) {
        GlobalUnlock(pGroup->hGroup);
        return;
    }

    /*
     * Repaint the original position
     */
    InvalidateIcon(pGroup, pItem);

    lpid = LockItem(pGroup,pItem);
    if (!lpid) {
        GlobalUnlock(pGroup->hGroup);
        return;
    }

    ComputeIconPosition(pGroup, pt, &pItem->rcIcon, &pItem->rcTitle,
            (LPTSTR) PTR(lpgd, lpid->pName));


    UnlockGroup(pGroup->hwnd);
    GlobalUnlock(pGroup->hGroup);

    /*
     * Repaint the new position
     */
    InvalidateIcon(pGroup,pItem);

//  CalcGroupScrolls(pGroup->hwnd);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  DropObject() -                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

LONG NEAR PASCAL DropObject(HWND hWnd, LPDROPSTRUCT lpds)
{
  BOOL          fNC;
  BOOL          fOk;
  POINT         pt;
  LPPOINT       lppt;
  PGROUP        pGroup;
  PITEM         pItem;
  RECT          rcClient;

  pGroup = pCurrentGroup;
  pItem = pGroup->pItems;

  pt = lpds->ptDrop;

  // A drop anywhere in the window is valid.
  GetWindowRect(hWnd, &rcClient);
  // Convert to client coords.
  ScreenToClient(hWnd,(LPPOINT)&(rcClient.left));
  ScreenToClient(hWnd,(LPPOINT)&(rcClient.right));

  if (pt.x >= rcClient.left && pt.y >= rcClient.top && pt.x <= rcClient.right
                                                && pt.y <= rcClient.bottom) {
      /* Dropped in given point of client area. */
      fNC = FALSE;
      pt.x -= (GetSystemMetrics(SM_CXICON) / 2) + 2;
      pt.y -= (GetSystemMetrics(SM_CYICON) / 2) + 2;
      lppt = &pt;
  }
  else {
      /* Dropped in nonclient area. */
      fNC = TRUE;
      lppt = NULL;
  }

  /* Are we iconic ? */
  if (IsIconic(hWnd)) {
      // Yep, we'll need to use default positioning.
      fNC = TRUE;
      lppt = NULL;
  }

#if 0
  // this if statement code if obsolete, it is never called. - johannec 8/11/93
  if (lpds->wFmt == DOF_EXECUTABLE || lpds->wFmt == DOF_DOCUMENT) {

      BuildDescription(szNameField, szPathName);

      return((LONG)(CreateNewItem(hWnd,
                szNameField, szPathName, szPathName, TEXT(""),
                0, FALSE, 0, 0, NULL, lppt, CI_SET_DOS_FULLSCRN) != NULL));
  }
#endif

  if ((hWnd == pGroup->hwnd) && (bMove)) {
      /* Don't drop on our own non-client area. */
      if (fNC)
          return 0L;

      /* We are just moving the item within its own group.
       * Hide it first so the icon title is treated correctly.
       */
      MoveItem(pGroup,pItem, pt);
      if ((bAutoArrange) && (!bAutoArranging))
          ArrangeItems(pGroup->hwnd);
      else if (!bAutoArranging)
          CalcGroupScrolls(pGroup->hwnd);

      return(DRAG_SWP);
  }
  else {
      /* Copy the item to the new group...  Set the hourglass
       * cursor (it will get unset after the message returns),
       * select the new group, and add the item at the specified
       * point.
       */
      fOk = DuplicateItem(pGroup,pItem,
                  (PGROUP)GetWindowLongPtr(hWnd,GWLP_PGROUP),lppt) != NULL;

      /*
       * Re-Arrange items within the destination group.
       * NB The source will been taken care of by the DeleteItem routine
       * called from DragItem.
       */
      if ((bAutoArrange) && (!bAutoArranging)) {
          /* Destination */
          ArrangeItems(hWnd);
      }
      else if (!bAutoArranging) {
          /* Destination */
          CalcGroupScrolls(hWnd);
      }

      /* View the current item. */
      BringItemToTop(pGroup,pItem, TRUE);
      ViewActiveItem(pGroup);

      /* If the dest isn't minimised then move the focus to it. */
      if (!IsIconic(hWnd))
           SetFocus(hWnd);
      return (fOk ? DRAG_COPY : 0L);
  }
}

LONG APIENTRY DropFiles(HWND hwnd, HANDLE hDrop)
{
    POINT pt;
    LPPOINT lppt;
    UINT i;
    HCURSOR hCursor;
    DWORD dwRet;
    DWORD dwFlags = CI_ACTIVATE | CI_SET_DOS_FULLSCRN;

    hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
    ShowCursor(TRUE);

    if (!DragQueryPoint(hDrop, &pt) ||
                   DragQueryFile(hDrop, (UINT)-1, NULL, 0) != 1) {
        lppt = NULL;
    }
    else {
        pt.x -= (GetSystemMetrics(SM_CXICON) / 2) + 2;
        pt.y -= (GetSystemMetrics(SM_CYICON) / 2) + 2;
        lppt = &pt;
    }

    for (i=0; DragQueryFile(hDrop, i, szPathField, MAXITEMPATHLEN); i++) {
        //
        // if filename or directory have spaces, put the path
        // between quotes.
        //
        CheckEscapes(szPathField, MAXITEMPATHLEN+1);

        /* Verify the file's existance... */
        dwRet = ValidatePath(hwndProgman, szPathField, NULL, szIconPath);
        if (dwRet == PATH_INVALID) {
            continue;
	    }
	else if (dwRet == PATH_INVALID_OK) {
	    dwFlags |= CI_NO_ASSOCIATION;
	    }

        BuildDescription(szNameField,szPathField);

        GetDirectoryFromPath(szPathField, szDirField);
        if (!InQuotes(szDirField)) {
            CheckEscapes(szDirField, MAXITEMPATHLEN+1);
        }

        HandleDosApps(szIconPath);

        if (!CreateNewItem(hwnd,
                      szNameField,                /* name*/
                      szPathField,                /* command*/
                      szIconPath ,                /* icon path*/
                      szDirField,                 /* no default dir*/
                      0,0,                        /* no hotkey, no min on run*/
                      0,0,0,                      /* default icon*/
                      lppt,                       /* at this point*/
                      dwFlags))
                break;
    }

    DragFinish(hDrop);

    ShowCursor(FALSE);
    SetCursor(hCursor);

    if ((bAutoArrange) && (!bAutoArranging))
        ArrangeItems(hwnd);
    else if (!bAutoArranging)
        CalcGroupScrolls(hwnd);

    return 1L;
}

LRESULT APIENTRY GroupWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    POINT pt;

    switch (uiMsg) {

    case WM_CREATE:
    {
        LPMDICREATESTRUCT lpmdics;

        lpmdics = (LPMDICREATESTRUCT)(((LPCREATESTRUCT)lParam)->lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_PGROUP, lpmdics->lParam);
        DragAcceptFiles(hwnd,TRUE);
        SetFocus(hwnd);
        break;
    }

    case WM_ERASEBKGND:
        if (IsIconic(hwnd)) {
            //
            // Erase background with the APPWORKSPACE color
            //
            RECT rc;

	    if (!hbrWorkspace)
		hbrWorkspace = CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
            GetUpdateRect(hwnd, &rc, FALSE);
            if (IsRectEmpty(&rc)) {
                GetClientRect(hwnd, &rc);
            }
            FillRect((HDC)wParam, &rc, hbrWorkspace);
        }
        else {
            goto DefProc;
        }
        break;

    case WM_PAINT:
        if (IsIconic(hwnd)) {
            DrawGroupIcon(hwnd);
        }
        else {
            PaintGroup(hwnd);
        }
        break;

    case WM_QUERYDRAGICON:
    {
        PGROUP pGroup;
        HICON hIcon = NULL;

        pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);

        if (pGroup->fCommon) {
            hIcon = LoadIcon(hAppInstance, (LPTSTR) MAKEINTRESOURCE(COMMGROUPICON));
        }
        else {
            hIcon = LoadIcon(hAppInstance, (LPTSTR) MAKEINTRESOURCE(PERSGROUPICON));
        }

        if (hIcon) {
            return((LRESULT)hIcon);
        }
        else {
            goto DefProc;
        }

        break;
    }

    case WM_LBUTTONDOWN:
        ClickOn(hwnd, MAKEPOINTS(lParam));
        break;

    case WM_MOUSEMOVE:
        if (wParam & MK_LBUTTON) {
            pt.x = (int)(MAKEPOINTS(lParam).x);
            pt.y = (int)(MAKEPOINTS(lParam).y);
            if (!IsRectEmpty(&rcDrag) && !PtInRect(&rcDrag, pt)
                                    && !fNoFileMenu && (dwEditLevel < 2)) {
                SetRect(&rcDrag,0,0,0,0);
                DragItem(hwnd);
            }
        }
        else {
            SetRect(&rcDrag,0,0,0,0);
        }
        break;

    case WM_LBUTTONUP:
        SetRect(&rcDrag,0,0,0,0);
        break;

    case WM_NCLBUTTONDBLCLK:
        if (IsIconic(hwnd) && (GetKeyState(VK_MENU) < 0)) {
            PostMessage(hwndProgman, WM_COMMAND, IDM_PROPS, 0L);
        } else {
            goto DefProc;
        }
        break;

    case WM_LBUTTONDBLCLK:

        if (ItemHitTest((PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP),
                        MAKEPOINTS(lParam))) {
            if (GetKeyState(VK_MENU) < 0) {
                if (!fNoFileMenu)
                    PostMessage(hwndProgman,WM_COMMAND,IDM_PROPS,0L);
            } else {
                PostMessage(hwndProgman,WM_COMMAND,IDM_OPEN,0L);
            }

        } else {
            /*
             * Check for Alt-dblclk on nothing to get new item.
             */
            if (GetKeyState(VK_MENU) < 0 && !fNoFileMenu &&
                             (dwEditLevel <= 1) && !(pCurrentGroup->fRO) ) {
                MyDialogBox(ITEMDLG, hwndProgman, NewItemDlgProc);
            }
        }
        break;

    case WM_VSCROLL:
    case WM_HSCROLL:
        ScrollMessage(hwnd,uiMsg,wParam,lParam);
        break;

    case WM_CLOSE:
        SendMessage(hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0L);
        break;

    case WM_SYSCOMMAND:
    {
        PGROUP pGroup;
        LPGROUPDEF lpgd;
        TCHAR szCommonGroupSuffix[MAXKEYLEN];
        TCHAR szCommonGroupTitle[2*MAXKEYLEN];

        if (wParam == SC_MINIMIZE) {
            //
            // if the group is common remove the common suffix from the group
            // window title
            //
            pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);
            if (pGroup->fCommon) {
                lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
                SetWindowText(hwnd, (LPTSTR) PTR(lpgd, lpgd->pName));
                GlobalUnlock(pGroup->hGroup);
            }
        }

        if (wParam == SC_RESTORE) {
            if (!LockGroup(hwnd)) {
                if (wLockError == LOCK_LOWMEM) {
                    MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_LOWMEM, NULL, MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
                    break;
                }
                else {
                    /*
                     * Lock failed for some other reason - hopefully just
                     * a group change.  Stop the icon group from being
                     * restored.
                     */
                    break;
                }
            }
            else {
        		    UnlockGroup(hwnd);
		      }
        }
    	  if ((wParam == SC_MAXIMIZE) || (wParam == SC_RESTORE)) {
            //
            // if the group is common add the common suffix to the group
            // window title
            //
            pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);
            if (pGroup->fCommon) {
                lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

                if (!lpgd)
                   goto DefProc;

                lstrcpy(szCommonGroupTitle, (LPTSTR) PTR(lpgd, lpgd->pName));
                GlobalUnlock(pGroup->hGroup);
                if (LoadString(hAppInstance, IDS_COMMONGRPSUFFIX, szCommonGroupSuffix,
                               CharSizeOf(szCommonGroupSuffix))) {
                    lstrcat(szCommonGroupTitle, szCommonGroupSuffix);
                }
                SetWindowText(pGroup->hwnd, szCommonGroupTitle);
           }
	        InvalidateRect(hwnd, NULL, 0);
        }
        if (wParam == SC_MAXIMIZE)
	         SetWindowLong(hwnd, GWL_STYLE,
	              (GetWindowLong(hwnd,GWL_STYLE) & ~(WS_HSCROLL | WS_VSCROLL)));
    	  goto DefProc;
			
    }
    case WM_SYSKEYDOWN:
        if (!CheckHotKey(wParam,lParam))
            goto DefProc;
        break;

    case WM_KEYDOWN:
        if (!CheckHotKey(wParam,lParam))
            KeyWindow(hwnd,(WORD)wParam);
        break;

    //IME Support
    //by yutakas 1992.10.22
    // When user input DBCS, go and activate icon which has that
    // DBCS charcter in the first of description.
    case WM_IME_REPORT:
        switch (wParam)
        {
            case IR_STRING:
                IMEStringWindow(hwnd,(HANDLE)lParam);
                return TRUE;
            default:
                goto DefProc;
        }
        break;

    case WM_CHAR:
        CharWindow(hwnd, (WORD) wParam);
        break;

    case WM_QUERYDROPOBJECT:
    {
        #define lpds ((LPDROPSTRUCT)lParam)

        PGROUP pGroup;

        pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);

        if (pGroup->fRO) {
            return FALSE;
        }

    	if (lpds->wFmt == OBJ_ITEM) {
    	    return TRUE;
        }
        #undef lpds
    	goto DefProc;
    }

    case WM_DROPOBJECT:
        #define lpds ((LPDROPSTRUCT)lParam)

        if (lpds->wFmt == OBJ_ITEM)
            return DropObject(hwnd, lpds);
        #undef lpds
        goto DefProc;

    case WM_DROPFILES:
        return DropFiles(hwnd,(HANDLE)wParam);

    case WM_NCACTIVATE:
    {
        PGROUP pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);

        if (pGroup->pItems != NULL) {
            InvalidateRect(hwnd,&pGroup->pItems->rcTitle,TRUE);
        }
        goto DefProc;
    }

    case WM_QUERYOPEN:
    {
        PGROUP pGroup;
        LPGROUPDEF lpgd;
        TCHAR szCommonGroupSuffix[MAXKEYLEN];
        TCHAR szCommonGroupTitle[2*MAXKEYLEN];

        //
        // if the group is common add the common suffix to the group
        // window title
        //
        pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);
        if (pGroup->fCommon) {
            lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

            if (!lpgd)
               goto DefProc;

            lstrcpy(szCommonGroupTitle,(LPTSTR) PTR(lpgd, lpgd->pName));
            GlobalUnlock(pGroup->hGroup);
            if (LoadString(hAppInstance, IDS_COMMONGRPSUFFIX, szCommonGroupSuffix,
                           CharSizeOf(szCommonGroupSuffix))) {
                lstrcat(szCommonGroupTitle, szCommonGroupSuffix);
            }
            SetWindowText(pGroup->hwnd, szCommonGroupTitle);
        }
        goto DefProc;
    }

    case WM_SIZE:
        lParam = DefMDIChildProc(hwnd, uiMsg, wParam, lParam);
        if (wParam != SIZEICONIC) {
            if ((bAutoArrange) && (!bAutoArranging)) {
                ArrangeItems(hwnd);
            } else if (!bArranging) {
                CalcGroupScrolls(hwnd);
            }
        }
        else {
            PGROUP pGroup;
            LPGROUPDEF lpgd;

            //
            // reset window text of common groups
            //
            pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);
            if (pGroup->fCommon) {
                lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
                SetWindowText(pGroup->hwnd, (LPTSTR) PTR(lpgd, lpgd->pName));
                GlobalUnlock(pGroup->hGroup);
            }
        }
        return lParam;

    case WM_MDIACTIVATE:
        if (!pCurrentGroup) {
            goto DefProc;
        }

        /*
         * If we are de-activating this window...
         */
        if (lParam == 0) {
            /*
             * We're the last window... punt.
             */
            pCurrentGroup = NULL;

        } else if (hwnd == (HWND)wParam) {
            /*
             * We're being deactivated.  Update pCurrentGroup
             * to the node being activated.
             */
            pCurrentGroup = (PGROUP)GetWindowLongPtr((HWND)lParam, GWLP_PGROUP);

        } else {
            SetFocus(hwnd);
        }

        goto DefProc;

    case WM_MENUSELECT:
        //
        // to handle F1 on group window system menu
        //

        if (lParam) {        /*make sure menu handle isn't null*/
            wMenuID =  GET_WM_COMMAND_ID(wParam, lParam);    /*get cmd from loword of wParam*/
            hSaveMenuHandle = (HANDLE)lParam;    /*Save hMenu into one variable*/
            wSaveFlags = HIWORD(wParam);/*Save flags into another*/
            bFrameSysMenu = FALSE;
        }

        break;

    default:
DefProc:
        return DefMDIChildProc(hwnd, uiMsg, wParam, lParam);
    }

    return 0L;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  ArrangeItems() -                                                        */
/*                                                                          */
/* Arranges iconic windows within a group.                                  */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID FAR PASCAL ArrangeItems(HWND hwnd)
{
    PGROUP pGroup;
    register PITEM pItem;
    PITEM pItemT;
    int xSlots;
    register int i;
    int j,k;
    RECT rc;
    LPGROUPDEF lpgd;
    PITEM rgpitem[CITEMSMAX];
    POINT pt;
    int t1, t2;
    LONG style;

    if (bAutoArranging || IsIconic(hwnd))
        return;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);
    if (!pGroup)
        return;

    /*
     * If the group is RO then don't rearrange the items, just update the
     * scroll bars
     */
    if (!GroupCheck(pGroup)) {
        CalcGroupScrolls(hwnd);
        return;
    }

    bAutoArranging = TRUE;
    SetRectEmpty(&rcArrangeRect);

    style = GetWindowLong(hwnd,GWL_STYLE);
    GetRealClientRect(hwnd,style&~(WS_VSCROLL|WS_HSCROLL),&rc);
    SetWindowLong(hwnd,GWL_STYLE,style);

    xSlots = (rc.right - rc.left)/cxArrange;
    if (xSlots < 1)
        xSlots = 1;

    /* sort the items by x location within a row, or by row if the
     * rows are different
     */
    k = 0;
    for (pItem = pGroup->pItems; pItem; pItem = pItem->pNext) {
        /* find nearest row
         */
        t1 = pItem->rcIcon.top + cyArrange/2;
        if (t1 >= 0)
	        t1 -= t1 % cyArrange;
        else
	        t1 += t1 % cyArrange;

        for (i = 0; i < k; i++) {
            pItemT = rgpitem[i];

            t2 = pItemT->rcIcon.top + cyArrange/2;
            if (t2 >= 0)
	            t2 -= t2 % cyArrange;
            else
	            t2 += t2 % cyArrange;

            if (t2 > t1)
                break;
            else if (t2 == t1 && pItemT->rcIcon.left > pItem->rcIcon.left)
                break;
        }

        for (j = k; j > i; j--) {
            rgpitem[j] = rgpitem[j-1];
        }

        rgpitem[i] = pItem;

        k++;
    }

    lpgd = LockGroup(hwnd);
    if (!lpgd) {
        bAutoArranging = FALSE;
        return;
    }

    bNoScrollCalc = TRUE;
    for (i = 0; i < k; i++) {
        pItem = rgpitem[i];

        /* cxOffset necessary to match (buggy???) win 3 USER
         */
        pt.x = (i%xSlots)*cxArrange + (cxArrange-cxIconSpace)/2 + cxOffset;
        pt.y = (i/xSlots)*cyArrange;

        MoveItem(pGroup,pItem,pt);
    }

    if (!IsRectEmpty(&rcArrangeRect))
        InvalidateRect(pGroup->hwnd,&rcArrangeRect,TRUE);

    UnlockGroup(hwnd);
    bNoScrollCalc = FALSE;
    CalcGroupScrolls(hwnd);

    bAutoArranging = FALSE;
}

/****************************************************************************
 *
 *  IMEStringWindow(hwnd,hstr)
 *
 *  Change activate item by the strings come from IME.
 *  When Get WM_IME_REPORT with IR_STRING,this function is called.
 *
 *                         by yutakas 1992.10.22
 *
 ****************************************************************************/
BOOL FAR PASCAL IMEStringWindow(HWND hwnd, HANDLE hStr)
{
    LPTSTR lpStr;
    LPGROUPDEF lpgd;
    LPITEMDEF lpid;
    PGROUP pGroup;
    PITEM pItem = NULL;
    PITEM pItemLast,pTItem;
    int nCnt = 0;
    int nTCnt = 0;
    BOOL ret = FALSE;

    if (!hStr)
        return ret;

    if (!(lpStr = GlobalLock(hStr)))
        return ret;

    pGroup = (PGROUP)GetWindowLongPtr(hwnd,GWLP_PGROUP);

    if (!pGroup->pItems)
        return ret;

    lpgd = LockGroup(hwnd);
    if (!lpgd)
        return ret;

#ifdef _DEBUG
{
TCHAR szDev[80];
OutputDebugString((LPTSTR)TEXT("In IME Winsdow\r\n"));
wsprintf ((LPTSTR)szDev,TEXT("IMEStringWindow: lpStr is %s \r\n"),lpStr);
OutputDebugString((LPSTR)szDev);
}
#endif


    // Search for item, skip the currently selected one.
    for ( pTItem = pGroup->pItems->pNext; pTItem; pTItem=pTItem->pNext)
    {
        lpid = ITEM(lpgd,pTItem->iItem);
        nTCnt = IMEWindowGetCnt(lpStr,(LPTSTR)PTR(lpgd,lpid->pName));
        if (nCnt < nTCnt)
        {
            nCnt = nTCnt;
            pItem = pTItem;
        }
    }

    lpid = ITEM(lpgd,pGroup->pItems->iItem);
    nTCnt = IMEWindowGetCnt(lpStr,(LPTSTR)PTR(lpgd,lpid->pName));

    if ((nCnt >= nTCnt) && pItem)
      {
        pItemLast = MakeFirstItemLast(pGroup);
        BringItemToTop(pGroup,pItem, FALSE);
        // Handle updates.
        InvalidateRect(pGroup->hwnd,&pItem->rcTitle,TRUE);
        InvalidateRect(pGroup->hwnd,&pItemLast->rcTitle,TRUE);
        ViewActiveItem(pGroup);
        ret = TRUE;
      }


    GlobalUnlock(hStr);
    UnlockGroup(hwnd);

#ifdef _DEBUG
{
TCHAR szDev[80];
wsprintf ((LPTSTR)szDev,TEXT("IMEStringWindow: ret is %s \r\n"),ret);
OutputDebugString((LPTSTR)szDev);
}
#endif
    return ret;
}

/****************************************************************************
 *
 *  IMEWindowGetCnt(LPSTR,LPSTR)
 *
 *  Compare strings from ahead and return the number of same character
 *
 *                         by yutakas 1992.10.22
 *
 *
 ****************************************************************************/
int FAR PASCAL IMEWindowGetCnt(LPTSTR lp1, LPTSTR lp2)
{
    int cnt = 0;

    while (*lp1 && *lp2)
    {
        // ToddB: This typecasting is to prevent lp1 and lp2 from being modified
        //      by CharUpper'ing one char at a time instead of the whole string
        if (CharUpper((LPTSTR)(DWORD_PTR)(BYTE)*lp1) ==
                CharUpper((LPTSTR)(DWORD_PTR)(BYTE)*lp2))
        {
            cnt++;
        }
        else
            break;

        lp1++;
        lp2++;
    }

    return (*lp1 ? 0 : cnt);
}
