/****************************************************************************/
/*                                                                          */
/*  PMGSEG.C -                                                              */
/*                                                                          */
/*      Program Manager Group Handling Routines                             */
/*                                                                          */
/****************************************************************************/

#include "progman.h"
#include "dde.h"
#include "convgrp.h"

#define WORD_MIN -32767
#define WORD_MAX  32767

#ifndef ORGCODE
#include "fcntl.h"
#include "io.h"
#include "stdio.h"
#include <tchar.h>
#define S_IREAD     0000400         /* read permission, owner */
#define S_IWRITE    0000200         /* write permission, owner */
#endif

BOOL fFirstLoad = FALSE;
extern BOOL bHandleProgramGroupsEvent;

#if 0
// DOS apps are no longer set to fullscreen by default in progman
//  5-3-93 johannec (bug 8343)
#ifdef i386
BOOL IsDOSApplication(LPTSTR lpPath);
BOOL SetDOSApplicationToFullScreen(LPTSTR lpTitle);
#endif
#endif

void NEAR PASCAL RemoveItemFromList(PGROUP pGroup, PITEM pItem)
    // Removes a PITEM from the list.
{
    PITEM *ppItem;

    /* Cause it to be repainted later. */
    InvalidateIcon(pGroup, pItem);

    if (pItem == pGroup->pItems) {
        /*
         * first one in list, must invalidate next one so it paints an active
         * title bar.
         */
        InvalidateIcon(pGroup,pItem->pNext);
    }

    /* Remove it from the list. */
    for (ppItem = &pGroup->pItems;*ppItem != pItem;
        ppItem = &((*ppItem)->pNext));

    *ppItem = pItem->pNext;

    /* Lastly free up the memory. */
    LocalFree((HANDLE)pItem);
}

#ifdef DEBUG
void NEAR PASCAL CheckBeforeReAlloc(HANDLE h)
{
        TCHAR buf[100];

        if ((BYTE)GlobalFlags(h)) {
                wsprintf(buf, TEXT("LockCount before realloc %d\r\n"), (BYTE)GlobalFlags(h));
                OutputDebugString(buf);
                DbgBreakPoint();
        }
}
#else
#define CheckBeforeReAlloc(h)
#endif

#ifdef PARANOID
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CheckRange() -                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void PASCAL CheckRange(
    LPGROUPDEF lpgd,
    LPTSTR lp1,
    WORD *lpw1,
    WORD cb1,
    LPTSTR lp2,
    WORD w2,
    WORD cb2,
    LPTSTR lpThing)
{
    WORD w1 = *lpw1;
    WORD e1, e2;

    if (!w1 || (w1 == w2)) {
        return;
    }

    if (!cb1) {
        cb1 = (WORD)lstrlen((LPTSTR) PTR(lpgd, *lpw1));
    }

    e1 = w1 + cb1;
    e2 = w2 + cb2;

    if ((w1 < e2) && (w2 < e1)) {
        KdPrint(("ERROR: %s overlaps %s in %s!!!!\r\n",lp2,lp1,lpThing));
    }
}


/*--------------------------------------------------------------------------*/
/*                                                                            */
/*  CheckPointer() -                                                            */
/*                                                                            */
/*--------------------------------------------------------------------------*/

void PASCAL CheckPointer(
    LPGROUPDEF lpgd,
    LPTSTR lp,
    WORD *lpw,
    WORD cb,
    WORD limit)
{
    LPITEMDEF lpid;
    int i;

    if (lpw == NULL || !*lpw) {
        KdPrint(("Warning: %s is NULL\r\n", lp));
        DebugBreak();
    }

    if (!cb) {
        cb = lstrlen((LPTSTR) PTR(lpgd, *lpw));
    }

    if (*lpw + cb > limit) {
        KdPrint(("ERROR: %s runs off end of group\r\n", lp));
        return;
    }

}


/*--------------------------------------------------------------------------*/
/*                                                                            */
/*  VerifyGroup() -                                                            */
/*                                                                            */
/*--------------------------------------------------------------------------*/

void PASCAL VerifyGroup(
    LPGROUPDEF lpgd)
{
    int       i;
    LPITEMDEF lpid;
    DWORD     limit = lpgd->cbGroup;

    KdPrint(("\r\nChecking Group %s\r\n",(LPTSTR) PTR(lpgd, lpgd->pName)));
    CheckPointer(lpgd, TEXT("Group Name"), &lpgd->pName, 0, limit);

    for (i = 0; i < (int)lpgd->cItems; i++) {
        if (!lpgd->rgiItems[i]) {
            continue;
        }

        lpid = ITEM(lpgd, i);
        KdPrint(("Checking item %d at %4.4X (%s):\r\n", i, lpgd->rgiItems[i],
                (LPTSTR) PTR(lpgd, lpid->pName)));
        CheckPointer(lpgd, TEXT("Itemdef"), lpgd->rgiItems + i, sizeof(ITEMDEF), limit);
        CheckPointer(lpgd, TEXT("Item name"), &lpid->pName, 0, limit);
        CheckPointer(lpgd, TEXT("item command"), &lpid->pCommand, 0, limit);
        CheckPointer(lpgd, TEXT("item icon path"), &lpid->pIconPath, 0, limit);
    }
}
#endif


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  IsGroupReadOnly() -                                                    */
/*                                                                          */
/*--------------------------------------------------------------------------*/

BOOL FAR PASCAL IsGroupReadOnly(LPTSTR szGroupKey, BOOL bCommonGroup)
{
    HKEY hkey;
    HKEY hkeyGroups;

    if (bCommonGroup)
       hkeyGroups = hkeyCommonGroups;
    else if (bUseANSIGroups)
        hkeyGroups = hkeyAnsiProgramGroups;
    else
        hkeyGroups = hkeyProgramGroups;

    if (!hkeyGroups)
        return(FALSE);

    if (!RegOpenKeyEx(hkeyGroups, szGroupKey, 0, DELETE | KEY_READ | KEY_WRITE, &hkey)){
        RegCloseKey(hkey);
        return(FALSE);
    }
    return(TRUE);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  GroupCheck() -                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

BOOL FAR PASCAL GroupCheck(PGROUP pGroup)
{
    if (!fExiting && IsGroupReadOnly(pGroup->lpKey, pGroup->fCommon)) {
        pGroup->fRO = TRUE;
        return FALSE;
    }
    pGroup->fRO = FALSE;
    return TRUE;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  MyDwordAlign() -                                                        */
/*                                                                          */
/*--------------------------------------------------------------------------*/

INT MyDwordAlign(INT wStrLen)
{
    return ((wStrLen + 3) & ~3);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  SizeofGroup() -                                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/
DWORD PASCAL SizeofGroup(LPGROUPDEF lpgd)
{
    LPPMTAG lptag;
    DWORD cbSeg;
    DWORD cb;

    cbSeg = (DWORD)GlobalSize(lpgd);

    lptag = (LPPMTAG)((LPSTR)lpgd+lpgd->cbGroup);

    if ((DWORD)((PCHAR)lptag - (PCHAR)lpgd +MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb))+4) <= cbSeg
        && lptag->wID == ID_MAGIC
        && lptag->wItem == (int)0xFFFF
        && lptag->cb == (WORD)(MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)) + 4)
        && *(PLONG)lptag->rgb == PMTAG_MAGIC)
      {
        while ((cb = (DWORD)((PCHAR)lptag - (PCHAR)lpgd + MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)))) <= cbSeg)
          {
            if (lptag->wID == ID_LASTTAG)
                return cb;
            (LPSTR)lptag += lptag->cb;
          }
      }
    return lpgd->cbGroup;
}

/*--------------------------------------------------------------------------*/
/*                                                                            */
/*  LockGroup() -                                                            */
/*                                                                            */
/*--------------------------------------------------------------------------*/

/* Given the handle to the group's window, lock the group segment and return
 * a pointer thereto.  Reloads the group segment if it is not in memory.
 */

LPGROUPDEF FAR PASCAL LockGroup(HWND hwndGroup)

{
  PGROUP     pGroup;
  LPGROUPDEF lpgd;
  WORD       status;
  LPTSTR      lpszKey;
  HKEY       hKey = NULL;
  LONG       err;
  DWORD      cbMaxValueLen = 0;
  FILETIME   ft;
  TCHAR       szClass[64];
  DWORD      dummy = 64;
  DWORD      cbSecDesc;
  HKEY       hkeyGroups;
  BOOL       bCommonGroup;

  wLockError = 0;   // No errors.

  /* Find the handle and try to lock it. */
  pGroup = (PGROUP)GetWindowLongPtr(hwndGroup, GWLP_PGROUP);
  lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

  /* If we got a non-NULL selector, return the pointer. */
  if (pGroup->fLoaded)
      return(lpgd);

  if (lpgd) {
      GlobalUnlock(pGroup->hGroup);
  }

  NukeIconBitmap(pGroup);        // invalidate the bitmap

  /* The group has been discarded, must reread the file... */
  lpszKey = pGroup->lpKey;

  pGroup->fRO = FALSE;

  bCommonGroup = pGroup->fCommon;
  if (bCommonGroup)
      hkeyGroups = hkeyCommonGroups;
  else if (bUseANSIGroups)
      hkeyGroups = hkeyAnsiProgramGroups;
  else
      hkeyGroups = hkeyProgramGroups;

  if (!hkeyGroups)
      goto RegError;

  /* Try to open the group key. */
  if (err = RegOpenKeyEx(hkeyGroups, lpszKey, 0,
                         DELETE | KEY_READ | KEY_WRITE,
                         &hKey)) {
      /* Try read-only access */
      if (err = RegOpenKeyEx(hkeyGroups, lpszKey, 0,
                         KEY_READ, &hKey) || !hKey) {
          status = IDS_NOGRPFILE;
          goto LGError1;
      }
      if (!bUseANSIGroups) {
          pGroup->fRO = TRUE;
      }
  }

  if (!(err = RegQueryInfoKey(hKey,
                              szClass,
                              &dummy,   // cbClass
                              NULL,     // Title index
                              &dummy,   // cbSubKeys
                              &dummy,   // cb Max subkey length
                              &dummy,   // max class len
                              &dummy,   // values count
                              &dummy,   // max value name length
                              &cbMaxValueLen,
                              &cbSecDesc,   // cb Security Descriptor
                              &ft))) {
      if (!pGroup->ftLastWriteTime.dwLowDateTime &&
                   !pGroup->ftLastWriteTime.dwHighDateTime)
          pGroup->ftLastWriteTime = ft;
      else if (pGroup->ftLastWriteTime.dwLowDateTime != ft.dwLowDateTime ||
               pGroup->ftLastWriteTime.dwHighDateTime != ft.dwHighDateTime ) {
          wLockError = LOCK_FILECHANGED;
          status = IDS_GRPHASCHANGED;
          if (!fExiting)     // Don't reload changed groups on exit.
              PostMessage(hwndProgman,WM_RELOADGROUP,(WPARAM)pGroup,0L);
          goto LGError2;
      }
  }

  /* Find the size of the file by seeking to the end. */
  if (cbMaxValueLen < sizeof(GROUPDEF)) {
      status = IDS_BADFILE;
      goto LGError2;
  }

  /* Allocate some memory for the thing. */
  CheckBeforeReAlloc(pGroup->hGroup);
  if (!GlobalReAlloc(pGroup->hGroup, (DWORD)cbMaxValueLen, GMEM_MOVEABLE)) {
      wLockError = LOCK_LOWMEM;
      status = IDS_LOWMEM;
      lpszKey = NULL;
      goto LGError2;
  }

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

  /* Read the whole group data into memory. */
  status = IDS_BADFILE;
  if (err = RegQueryValueEx(hKey, NULL, 0, 0, (LPBYTE)lpgd, &cbMaxValueLen)) {
      goto LGError3;
  }
  //
  // If we start out from the ANSI groups, we need the security description
  // to copy the entire information to the UNICODE groups
  //
  if (bUseANSIGroups) {
      pGroup->pSecDesc = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, cbSecDesc);
      RegGetKeySecurity(hKey, DACL_SECURITY_INFORMATION, pGroup->pSecDesc, &cbSecDesc);
  }
  else {
      pGroup->pSecDesc = NULL;
  }

  //
  // If we loaded an old format ANSI group, then convert it to the
  // UNICODE format and save it back in the registry.
  //
  if (lpgd->dwMagic == GROUP_MAGIC) {
      HANDLE hUNIGroup;

      if (cbMaxValueLen = ConvertToUnicodeGroup((LPGROUPDEF_A)lpgd, &hUNIGroup)) {
          UnlockGroup(hwndGroup);
          /* Free the ANSI group. */
          GlobalFree(pGroup->hGroup);
          pGroup->hGroup = hUNIGroup;
          lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
      }
      else {
          goto LGError3;
      }
  }

  if (lpgd->dwMagic != GROUP_UNICODE)
      goto LGError3;

  if (lpgd->cbGroup > cbMaxValueLen)
      goto LGError3;

  /* Now return the pointer. */
  RegCloseKey(hKey);

  return(lpgd);

LGError3:
  GlobalUnlock(pGroup->hGroup);
  GlobalDiscard(pGroup->hGroup);
  pGroup->fLoaded = FALSE;

LGError2:
  RegCloseKey(hKey);

LGError1:
  if (status != IDS_LOWMEM && status != IDS_GRPHASCHANGED && status != IDS_NOGRPFILE) {
      MyMessageBox(hwndProgman, IDS_GROUPFILEERR, status, pGroup->lpKey,
                   MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
      if (status == IDS_BADFILE) {
          //
          // stop handling of Program Groups key changes.
          //
          bHandleProgramGroupsEvent = FALSE;

          RegDeleteKey(hkeyGroups, lpszKey);

          //
          // reset handling of Program Groups key changes.
          //
          ResetProgramGroupsEvent(bCommonGroup);
          bHandleProgramGroupsEvent = TRUE;
      }
      return(NULL);
  }

  /*
   * Special case the group not being found so we can delete it's entry...
   */
  if (status == IDS_NOGRPFILE) {
      /*
       * If no restrictions then we can fixup progman.ini...
       */
      if (!fNoSave && dwEditLevel < 1) {
          TCHAR szGroup[10];

          if (MyMessageBox(hwndProgman,IDS_GROUPFILEERR,IDS_NOGRPFILE2,lpszKey, MB_YESNO | MB_ICONEXCLAMATION | MB_DEFBUTTON1 | MB_SYSTEMMODAL) == IDNO) {
              wsprintf(szGroup,TEXT("Group%d"),pGroup->wIndex);
              //
              // stop handling of Program Groups key changes.
              //
              bHandleProgramGroupsEvent = FALSE;
              RegDeleteKey(hkeyProgramGroups, lpszKey);

              //
              // reset handling of Program Groups key changes.
              //
              ResetProgramGroupsEvent(bCommonGroup);
              bHandleProgramGroupsEvent = TRUE;
              RegDeleteValue(hkeyPMGroups, szGroup);

              if (!fFirstLoad)
                  PostMessage(hwndProgman,WM_UNLOADGROUP,(WPARAM)hwndGroup,0L);
          }
      }
      else {
RegError:
          /*
           * Restrictions mean that the user can only OK this error...
           */
          MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_NOGRPFILE, lpszKey,
                           MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
      }
  }

  ShowWindow(hwndGroup, SW_SHOWMINNOACTIVE);

  return(NULL);
}


/*--------------------------------------------------------------------------*/
/*                                                                            */
/*  UnlockGroup() -                                                            */
/*                                                                            */
/*--------------------------------------------------------------------------*/

void FAR PASCAL UnlockGroup(register HWND hwndGroup)

{
  GlobalUnlock(((PGROUP)GetWindowLongPtr(hwndGroup,GWLP_PGROUP))->hGroup);
}

/*--------------------------------------------------------------------------*/
/*                                                                            */
/*  LockItem() -                                                            */
/*                                                                            */
/*--------------------------------------------------------------------------*/

LPITEMDEF FAR PASCAL LockItem(PGROUP pGroup, PITEM pItem)
{
  LPGROUPDEF        lpgd;

  lpgd = LockGroup(pGroup->hwnd);

  if (!lpgd)
      return((LPITEMDEF)NULL);

  return ITEM(lpgd,pItem->iItem);
}


/*--------------------------------------------------------------------------*/
/*                                                                            */
/*  KeepGroupAround() -                                                     */
/*                                                                            */
/*--------------------------------------------------------------------------*/

/*
 * Sets or unsets the discardable flag for the given group file.  If setting
 * to non-discard, forces the group to be in memory.
 */

HANDLE PASCAL KeepGroupAround(HWND hwndGroup, BOOL fKeep)
{
    PGROUP pGroup;

    UNREFERENCED_PARAMETER(fKeep);
    pGroup = (PGROUP)GetWindowLongPtr(hwndGroup, GWLP_PGROUP);
    return pGroup->hGroup;

#ifdef ORGCODE
    PGROUP pGroup;
    WORD flag;

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

    if (fKeep) {
        if (LockGroup(hwndGroup)) {
            UnlockGroup(hwndGroup);  // it is still in memory
        } else {
            return NULL; // failure
        }

        flag = GMEM_MODIFY | GMEM_MOVEABLE;  // make non discardable
    } else {
        flag = GMEM_MODIFY | GMEM_MOVEABLE | GMEM_DISCARDABLE;  // discardable
    }

    return GlobalReAlloc(pGroup->hGroup, 0, flag);
#endif
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  SaveGroup() -                                                           */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/*
 * Writes out a group file.  It must already be in memory or the operation
 * is meaningless.
 */
BOOL APIENTRY SaveGroup(
    HWND hwndGroup, BOOL bDiscard
    )
{
    LPGROUPDEF lpgd;
    HKEY       hKey;
    PGROUP     pGroup;
    WORD       status = 0;
    DWORD      cb;
    LONG       err;
    HKEY       hkeyGroups;
    BOOL       bCommonGroup;
    DWORD      dwDisposition;

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

    bCommonGroup = pGroup->fCommon;

    if (!bUseANSIGroups && IsGroupReadOnly(pGroup->lpKey, bCommonGroup)) {
        // Don't produce an error message for RO groups.
        return FALSE;
    }

    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
    if (!lpgd) {
        return FALSE;
    }

    if (bCommonGroup)
        hkeyGroups = hkeyCommonGroups;
    else
        hkeyGroups = hkeyProgramGroups;

    if (!hkeyGroups) {
        goto Exit1;
    }

    // it may already exist

    if (err = RegCreateKeyEx(hkeyGroups, pGroup->lpKey, 0, 0, 0,
                     DELETE | KEY_READ | KEY_WRITE | WRITE_DAC,
                     pSecurityAttributes, &hKey, &dwDisposition)) {
    //if (err = RegOpenKeyEx(hkeyGroups, pGroup->lpKey, 0,
    //                              KEY_SET_VALUE, &hKey)) {
        /*
         * We can't open output group key.
         */
        if (err = RegOpenKeyEx(hkeyGroups, pGroup->lpKey, 0,
                                  KEY_READ, &hKey)) {
            status = IDS_NOGRPFILE;
        } else {
            // status = IDS_GRPISRO;
            RegCloseKey(hKey);
        }
        goto Exit1;
    }
    else {
        if (dwDisposition == REG_CREATED_NEW_KEY && bUseANSIGroups) {
            RegSetKeySecurity(hKey, DACL_SECURITY_INFORMATION, pGroup->pSecDesc);
            LocalFree(pGroup->pSecDesc);
            pGroup->pSecDesc = NULL;
        }

    }

    //
    // stop handling Program Groups key changes for a SAveGroup.
    //
    bHandleProgramGroupsEvent = FALSE;

    cb = SizeofGroup(lpgd);
    if (err = RegSetValueEx(hKey, NULL, 0, REG_BINARY, (LPBYTE)lpgd, cb)) {
        status = IDS_CANTWRITEGRP;
    }

    RegFlushKey(hKey);
    RegCloseKey(hKey);

    pGroup->ftLastWriteTime.dwLowDateTime = 0;   // update file time stamp if we need to reload
    pGroup->ftLastWriteTime.dwHighDateTime = 0;

Exit1:
    GlobalUnlock(pGroup->hGroup);

    if (status && !fExiting) {
        MyMessageBox(hwndProgman, IDS_GROUPFILEERR, status, pGroup->lpKey,
                MB_OK | MB_ICONEXCLAMATION);

        /*
         * Force the group to be reset.
         */
        if (bDiscard) {
            GlobalDiscard(pGroup->hGroup);
            pGroup->fLoaded = FALSE;
            InvalidateRect(pGroup->hwnd, NULL, TRUE);
        }
    }

    //
    // reset handling of Program Groups key changes.
    //
    ResetProgramGroupsEvent(bCommonGroup);
    bHandleProgramGroupsEvent = TRUE;
    return (status == 0);
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  AdjustPointers() -                                                      */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/*
 * Adjusts pointers in the segment after a section is moved up or down.
 */
void PASCAL AdjustPointers(LPGROUPDEF lpgd, DWORD iFirst, DWORD di)
{
    WORD i;
    LPITEMDEF lpid;

    if (lpgd->pName >= iFirst) {
        lpgd->pName += di;
    }

    for (i = 0; i < lpgd->cItems; i++) {
        if (!lpgd->rgiItems[i]) {
            continue;
        }

        if (lpgd->rgiItems[i] >= iFirst) {
            lpgd->rgiItems[i] += di;
        }

        lpid = ITEM(lpgd, i);

        if (lpid->pIconRes >= iFirst)
            lpid->pIconRes += di;
        if (lpid->pName >= iFirst)
            lpid->pName += di;
        if (lpid->pCommand >= iFirst)
            lpid->pCommand += di;
        if (lpid->pIconPath >= iFirst)
            lpid->pIconPath += di;
    }
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  FindFreeItemIndex() -                                                   */
/*                                                                          */
/* Returns the index of a free slot in the item offset array.  If necessary,*/
/* moves stuff around.                                                      */
/*                                                                          */
/*--------------------------------------------------------------------------*/

WORD PASCAL FindFreeItemIndex(HWND hwndGroup)
{
    LPGROUPDEF lpgd;
    PGROUP     pGroup;
    WORD       i;
    LPTSTR      lp1;
    LPTSTR      lp2;
    DWORD      cb;

    lpgd = LockGroup(hwndGroup);
    if (!lpgd) {
        return(0xFFFF);
    }

    for (i = 0; i < lpgd->cItems; i++) {
        if (!lpgd->rgiItems[i]) {
            UnlockGroup(hwndGroup);
            return(i);
        }
    }

    /*
     * Didn't find an empty slot... make some new ones.
     */
    pGroup = (PGROUP)GetWindowLongPtr(hwndGroup, GWLP_PGROUP);

    // Current groups+tags size.
    cb = SizeofGroup(lpgd);

    // Increase space reserved item info.
    lpgd->cbGroup += NSLOTS*sizeof(DWORD);

    // Increase size of whole group.
    cb += NSLOTS*sizeof(DWORD);

    UnlockGroup(hwndGroup);

    CheckBeforeReAlloc(pGroup->hGroup);
    if (!GlobalReAlloc(pGroup->hGroup, cb, GMEM_MOVEABLE)) {
        return 0xFFFF;
    }

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

    /*
     * Copy tags junk (which starts at the end of the rgiItems array)
     * up a bit to make room for the bigger array..
     */
    lp1 = (LPTSTR)&(lpgd->rgiItems[lpgd->cItems]);
    lp2 = (LPTSTR)&(lpgd->rgiItems[lpgd->cItems + NSLOTS]);

    /*
     * Copy everything down in the segment.
     */
    RtlMoveMemory(lp2, lp1, (WORD)(cb - (DWORD)((LPSTR)lp2 - (LPSTR)lpgd)));

    /*
     * Zero out the new offsets.
     */
    for (i = (WORD)lpgd->cItems; i < (WORD)(lpgd->cItems + NSLOTS); i++) {
        lpgd->rgiItems[i] = 0;
    }

    i = lpgd->cItems;

    /* Record that we now have more slots */
    lpgd->cItems += NSLOTS;

    /*
     * Fix up all the offsets in the segment.  Since the rgiItems array is
     * part of the group header, all the pointers will change.
     */
    AdjustPointers(lpgd, (WORD)1, NSLOTS * sizeof(DWORD));

    GlobalUnlock(pGroup->hGroup);

    return i;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  DeleteThing() -                                                         */
/*                                                                          */
/*                                                                          */
/* Removes a part of the group segment.  Updates everything in the segment  */
/* but does not realloc.                                                    */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void NEAR PASCAL DeleteThing(LPGROUPDEF lpgd, LPDWORD lpiThing, WORD cbThing)
{
  DWORD       dwThingOffset;
  LPTSTR      lp1;
  LPTSTR      lp2;
  INT        cb;
  WORD       cbThingSize;

  if (cbThing == 0xFFFF) {
      return;
  }

  dwThingOffset = *lpiThing;

  if (!dwThingOffset)
      return;

  *lpiThing = 0;

  lp1 = (LPTSTR) PTR(lpgd, dwThingOffset);

  /* If its a string we're removing, the caller can pass 0 as the length
   * and have it calculated!!!
   */
  if (!cbThing) {
      cbThing = (WORD)sizeof(TCHAR)*(1 + lstrlen(lp1));
  }

  cbThingSize = (WORD)MyDwordAlign((int)cbThing);

  lp2 = (LPTSTR)((LPBYTE)lp1 + cbThingSize);

  cb = (int)SizeofGroup(lpgd);

  RtlMoveMemory(lp1, lp2, (cb - (DWORD)((LPSTR)lp2 - (LPSTR)lpgd)));

  lpgd->cbGroup -= cbThingSize;

  AdjustPointers(lpgd, dwThingOffset, -cbThingSize);

}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  AddThing() -                                                            */
/*                                                                          */
/* in:                                                                      */
/*	hGroup	group handle, must not be discardable                           */
/*	lpStuff	pointer to data or NULL to init data to zero                    */
/*	cbStuff	count of item (may be 0) if lpStuff is a string                 */
/*                                                                          */
/* Adds an object to the group segment and returns its offset.	Will        */
/* reallocate the segment if necessary.                                     */
/*                                                                          */
/* Handle passed in must not be discardable                                 */
/*                                                                          */
/* returns:                                                                 */
/*	0	failure                                                             */
/*	> 0	offset to thing in the segment                                      */
/*                                                                          */
/*--------------------------------------------------------------------------*/

DWORD PASCAL AddThing(HANDLE hGroup, LPTSTR lpStuff, DWORD cbStuff)
{
    DWORD        cb;
    LPGROUPDEF  lpgd;
    DWORD        offset;
    LPTSTR       lpT;
    DWORD        cbStuffSize;
    DWORD        cbGroupSize;
    DWORD        myOffset;

    if (cbStuff == 0xFFFFFFFF) {
        return 0xFFFFFFFF;
    }

    if (!cbStuff) {
        cbStuff = sizeof(TCHAR)*(DWORD)(1 + lstrlen(lpStuff));
    }

    cbStuffSize = MyDwordAlign((int)cbStuff);

    lpgd = (LPGROUPDEF)GlobalLock(hGroup);
    cb = SizeofGroup(lpgd);
    cbGroupSize = MyDwordAlign((int)cb);

    offset = lpgd->cbGroup;
    myOffset = (DWORD)MyDwordAlign((int)offset);

    GlobalUnlock(hGroup);

    CheckBeforeReAlloc(hGroup);
    if (!GlobalReAlloc(hGroup,(DWORD)(cbGroupSize + cbStuffSize), GMEM_MOVEABLE))
        return 0;

    lpgd = (LPGROUPDEF)GlobalLock(hGroup);

    /*
     * Slide the tags up
     */
    RtlMoveMemory((LPSTR)lpgd + myOffset + cbStuffSize, (LPSTR)lpgd + myOffset,
                            (cbGroupSize - myOffset));
    lpgd->cbGroup += cbStuffSize;

    lpT = (LPTSTR)((LPSTR)lpgd + myOffset);
    if (lpStuff) {
        RtlMoveMemory(lpT, lpStuff, cbStuff);

    } else {
        /*
         * Zero it
         */
        while (cbStuffSize--) {
            *((LPSTR)lpT)++ = 0;
        }
    }


    GlobalUnlock(hGroup);

    return myOffset;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  FindTag() -                                                             */
/*                                                                          */
/*--------------------------------------------------------------------------*/

LPPMTAG NEAR PASCAL FindTag(LPGROUPDEF lpgd, int item, WORD id)
{
    LPPMTAG lptag;
    int cbSeg;
    int cb;

    cbSeg = (DWORD)GlobalSize(lpgd);

    lptag = (LPPMTAG)((LPSTR)lpgd+lpgd->cbGroup);

    if ((PCHAR)lptag - (PCHAR)lpgd + MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)) + 4 <= cbSeg
        && lptag->wID == ID_MAGIC
        && lptag->wItem == (int)0xFFFF
        && lptag->cb == (WORD)(MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)) +4)
        && *(LONG FAR *)lptag->rgb == PMTAG_MAGIC) {

        while ((cb = (int)((PCHAR)lptag - (PCHAR)lpgd + MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)))) <= cbSeg)
        {
            if ((item == lptag->wItem)
                && (id == 0 || id == lptag->wID)) {
                return lptag;
            }

            if (lptag->wID == ID_LASTTAG)
                return NULL;

            (LPSTR)lptag += lptag->cb;
        }
    }
    return NULL;
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CopyTag() -                                                             */
/*                                                                          */
/*--------------------------------------------------------------------------*/

INT FAR PASCAL CopyTag(LPGROUPDEF lpgd, int item, WORD id, LPTSTR lpbuf, int cb)
{
    LPTSTR lpt;
    LPPMTAG lptag;
    WORD cbT;

    lptag = FindTag(lpgd,item,id);

    if (lptag == NULL)
        return 0;

    if (cb > (int)lptag->cb)
        cb = lptag->cb;

    cbT = (WORD)cb;

    lpt = (LPTSTR) lptag->rgb;

    while (*lpt && cbT) {
       *lpbuf++=*lpt++;
       cbT--;
    }

    if (!(*lpt) && cbT) {
        *lpbuf = TEXT('\0');
    }

    return cb;
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  DeleteTag() -                                                           */
/*                                                                          */
/* in:                                                                      */
/*	hGroup	group handle, can be discardable (alwayws shrink object)        */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID FAR PASCAL DeleteTag(HANDLE hGroup, int item, WORD id)
{
    LPPMTAG lptag;
    LPTSTR lp1, lp2;
    LPTSTR lpend;
    LPGROUPDEF lpgd;

    lpgd = (LPGROUPDEF) GlobalLock(hGroup);

    lptag = FindTag(lpgd,item,id);

    if (lptag == NULL) {
        GlobalUnlock(hGroup);
        return;
    }

    lp1 = (LPTSTR)lptag;

    lp2 = (LPTSTR)((LPSTR)lptag + lptag->cb);

    lpend = (LPTSTR)((LPSTR)lpgd + SizeofGroup(lpgd));

    while (lp2 < lpend) {
        *lp1++ = *lp2++;
    }

    /* always reallocing smaller
     */
    GlobalUnlock(hGroup);
    CheckBeforeReAlloc(hGroup);
    GlobalReAlloc(hGroup, (DWORD)((LPSTR)lp1 - (LPSTR)lpgd), 0);

    return;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  AddTag() -                                                              */
/*                                                                          */
/* in:                                                                      */
/*	h	group handle, must not be discardable!                              */
/*                                                                          */
/* returns:                                                                 */
/*  0	failure                                                             */
/*	1	success                                                             */
/*--------------------------------------------------------------------------*/
INT PASCAL AddTag(HANDLE h, int item, WORD id, LPTSTR lpbuf, int cb)
{
    LPPMTAG lptag;
    WORD fAddFirst;
    LPGROUPDEF lpgd;
    int cbNew;
    int cbMyLen;
    LPGROUPDEF lpgdOld;


    if (!cb && lpbuf) {
        cb = sizeof(TCHAR)*(lstrlen(lpbuf) + 1);
    }
    cbMyLen = MyDwordAlign(cb);

    if (!lpbuf) {
        cb = 0;
        cbMyLen = 0;
    }

    /*
     * Remove the old version of the tag, if any.
     */
    DeleteTag(h, item, id);

    lpgd = (LPGROUPDEF)GlobalLock(h);

    lptag = FindTag(lpgd, (int)0xFFFF, (WORD)ID_LASTTAG);

    if (!lptag) {
        /*
         * In this case, there are no tags at all, and we have to add
         * the first tag, the interesting tag, and the last tag
         */
        cbNew = 3 * (MyDwordAlign(sizeof(PMTAG)) - MyDwordAlign(sizeof(lptag->rgb))) + 4 + cbMyLen;
        fAddFirst = TRUE;
        lptag = (LPPMTAG)((LPSTR)lpgd + lpgd->cbGroup);

    } else {
        /*
         * In this case, only the interesting tag needs to be added
         * but we count in the last because the delta is from lptag
         */
        cbNew = 2 * (MyDwordAlign(sizeof(PMTAG)) - MyDwordAlign(sizeof(lptag->rgb))) + cbMyLen;
        fAddFirst = FALSE;
    }

    /*
     * check for 64K limit
     */
    if ((DWORD_PTR)lptag + cbNew < (DWORD_PTR)lptag) {
        return 0;
    }

    cbNew += (int)((PCHAR)lptag -(PCHAR)lpgd);
    lpgdOld = lpgd;
    GlobalUnlock(h);
    CheckBeforeReAlloc(h);
    if (!GlobalReAlloc(h, (DWORD)cbNew, GMEM_MOVEABLE)) {
        return 0;
    }

    lpgd = (LPGROUPDEF)GlobalLock(h);
    lptag = (LPPMTAG)((LPSTR)lpgd + ((LPSTR)lptag - (LPSTR)lpgdOld));
    if (fAddFirst) {
        /*
         * Add the first tag
         */
        lptag->wID = ID_MAGIC;
        lptag->wItem = (int)0xFFFF;
        *(LONG FAR *)lptag->rgb = PMTAG_MAGIC;
        lptag->cb = (WORD)(MyDwordAlign(sizeof(PMTAG)) - MyDwordAlign(sizeof(lptag->rgb)) + 4);
        (LPSTR)lptag += lptag->cb;
    }

    /*
     * Add the tag
     */
    lptag->wID = id;
    lptag->wItem = item;
    lptag->cb = (WORD)(MyDwordAlign(sizeof(PMTAG)) - MyDwordAlign(sizeof(lptag->rgb)) + cbMyLen);
    if (lpbuf) {
        RtlMoveMemory(lptag->rgb, lpbuf, (WORD)cb);
    }
    (LPSTR)lptag += lptag->cb;

    /*
     * Add the end tag
     */
    lptag->wID = ID_LASTTAG;
    lptag->wItem = (int)0xFFFF;
    lptag->cb = 0;

    GlobalUnlock(h);

    return 1;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  NukeIconBitmap -- Deletes the icon bitmap if one exists for the group   */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void PASCAL NukeIconBitmap(PGROUP pGroup)
{
    if (pGroup->hbm) {
        DeleteObject(pGroup->hbm);
        pGroup->hbm = NULL;
    }
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  GroupFlag() -                                                           */
/*                                                                          */
/*--------------------------------------------------------------------------*/

WORD PASCAL GroupFlag(PGROUP pGroup, PITEM pItem, WORD wFlag)
{
    LPGROUPDEF lpgd;
    LPPMTAG lptag;
    WORD wT = 0;
    int wItem;

    if (pItem) {
        wItem = pItem->iItem;
    } else {
        wItem = (int)0xFFFF;
    }

    lpgd = LockGroup(pGroup->hwnd);
    if (!lpgd)
        return 0;

    lptag = FindTag(lpgd, wItem, wFlag);

    if (!lptag)
        wT = 0;
    else if (lptag->cb == (WORD)(MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb))))
        wT = 1;
    else if (lptag->cb == (WORD)(MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)) + 1))
        wT = lptag->rgb[0];
    else if (lptag->cb == (WORD)(MyDwordAlign(sizeof(PMTAG))-MyDwordAlign(sizeof(lptag->rgb)) + 4))
        wT = *(LPWORD)lptag->rgb;
    UnlockGroup(pGroup->hwnd);

    return wT;
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  GetGroupTag() -                                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/

WORD PASCAL GetGroupTag(
    PGROUP pGroup,
    PITEM pItem,
    WORD id,
    LPTSTR lpT,
    WORD cb)
{
    WORD wT;
    LPGROUPDEF lpgd;
    int wItem;

    if (pItem)
        wItem = pItem->iItem;
    else
        wItem = (int)0xFFFF;

    lpgd = LockGroup(pGroup->hwnd);
    if (!lpgd)
        return 0;

    wT = (WORD)CopyTag(lpgd, wItem, id, lpT, cb);

    UnlockGroup(pGroup->hwnd);
    return wT;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  ChangeTagID() -                                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void PASCAL ChangeTagID(
    LPGROUPDEF lpgd,
    int iOld,
    int iNew)
{
    LPPMTAG lptag;

    while (lptag = FindTag(lpgd,iOld,0)) {
        lptag->wItem = iNew;
    }
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  LoadItem() -                                                            */
/*                                                                          */
/* Creates an item window (iconic) within a group window.  Assumes that the */
/* group segment is up to date.                                             */
/*                                                                          */
/*--------------------------------------------------------------------------*/

PITEM PASCAL LoadItem(HWND hwndGroup, WORD iItem, BOOL bActivate)
{
    LPGROUPDEF lpgd;
    LPITEMDEF  lpid;
    PGROUP     pGroup;
    PITEM      pItem;
    PITEM      *ppItem;

    lpgd = LockGroup(hwndGroup);
    if (!lpgd)
        return NULL;

    pItem = (PITEM)LocalAlloc(LPTR, sizeof(ITEM));
    if (!pItem) {
        UnlockGroup(hwndGroup);
        return NULL;
    }

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

    NukeIconBitmap(pGroup);

    if (bActivate) {
        InvalidateIcon(pGroup, pGroup->pItems);
        pItem->pNext = pGroup->pItems;
        pGroup->pItems = pItem;
    } else {
        ppItem = &pGroup->pItems;
        while (*ppItem) {
            ppItem = &((*ppItem)->pNext);
        }
        pItem->pNext = NULL;
        *ppItem = pItem;
    }

    lpid = ITEM(lpgd, iItem);

    pItem->iItem = iItem;
    pItem->dwDDEId = 0;
    SetRectEmpty(&pItem->rcTitle);
    SetRectEmpty(&pItem->rcIcon);

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

    UnlockGroup(hwndGroup);

    InvalidateIcon(pGroup, pItem);

    return pItem;
}


PITEM FindItemName(LPGROUPDEF lpgd, register PITEM pItem, LPTSTR lpTitle)
{
  LPITEMDEF  lpid;

  while (pItem) {
      lpid = ITEM(lpgd, pItem->iItem);

      if (!lstrcmp(lpTitle, (LPTSTR) PTR(lpgd, lpid->pName)))
        return pItem;

      pItem = pItem->pNext;
  }

  return NULL;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CreateNewItem() -                                                       */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/*
 * Creates a new item in the file, and adds a window for it.
 */
PITEM PASCAL CreateNewItem(
    HWND    hwndGroup,
    LPTSTR   lpTitle,
    LPTSTR   lpCommand,
    LPTSTR   lpIconPath,
    LPTSTR   lpDefDir,
    WORD    wHotKey,
    BOOL    fMinimize,
    WORD    wIconId,
    WORD    wIconIndex,
    HICON   hIcon,
    LPPOINT lppt,
    DWORD   dwFlags)
{
    LPGROUPDEF lpgd;
    LPITEMDEF  lpid;
    WORD       id;
    DWORD      offset;
    PGROUP     pGroup;
    LPTSTR     lpIconRes;
    WORD       cbIconRes;
    //DWORD    dwVer;
    WORD       wVer;
    WORD       idError = IDS_LOWMEM;
    PITEM      pItem;
    DWORD      cb;
    TCHAR      szCommand[3*MAX_PATH];
    TCHAR      szExeDir[MAXITEMPATHLEN + 1];
    TCHAR      szIconExe[MAX_PATH];
    TCHAR      szTemp[MAXITEMPATHLEN+1];
    LPTSTR     lp1, lp2, lp3;
    HANDLE     hIconRes;
    HANDLE     hModule;
    BOOL       fWin32App = FALSE;
    BOOL       fUseDefaultIcon = FALSE;
    BOOL       bNoIconPath = TRUE;
    TCHAR      cSeparator;

    /*
     * Before we do anything, whack the command line and exedir
     */
    lp1 = lpCommand;
    if (*lpCommand == TEXT('"') && wcschr(lpCommand + 1, TEXT('"'))) {
        cSeparator = TEXT('"');
        //lp1++;
    }
    else {
        cSeparator = TEXT(' ');
    }
    for (lp2=lp3=szExeDir; *lp1 && *lp1 != cSeparator; lp1 = CharNext(lp1))
    {
        *lp2++ = *lp1;

        /*
         * We know we're looking at the first byte
         */
        if ((*lp1 == TEXT(':')) || (*lp1 == TEXT('\\'))) {
            lp3 = lp2;
        }
    }

    *lp2 = 0;

    /*
     * If the default dir pointer is NULL then we use the directory
     * component of the command line.  Otherwise we do the normal
     * path whacking stuff to get everything into 3.0 format.
     */
    if (lpDefDir) {
        LPTSTR lpT;

        lpT = lpDefDir;
        // We have a valid pointer.
    	lstrcpy(szCommand,lpDefDir);
#if 0
/* spaces are allowed in LFN.
 */
        RemoveLeadingSpaces(szCommand);
#endif

        // If a default dir was supplied then go ahead and whack it
        // into 3.0 format otherwise leave it blank.
        if (*lpDefDir)
        {
            LPTSTR lpNextChar;

            // locate the character before the NULL
            while ( *(lpNextChar = CharNext(lpDefDir)) )
                lpDefDir = lpNextChar;

            // If there is no '\' seperator, add one.
            if (lpDefDir[0] != TEXT('\\')) {
                lstrcat(szCommand,TEXT("\\"));
            }
        }

        /*
         * Now add the filename itself.  this puts the command in the
         * 3.0 format: defdir\exename
         */
        lstrcat(szCommand, lp3);

        /*
         * Append the arguments
         */
        lstrcat(szCommand, lp1);
        lpDefDir = lpT;

    } else {
        /*
         * Use the same command line (note def dir is assigned exe dir
         */
        lstrcpy(szCommand, lpCommand);
    }

    /*
     * Now truncate exedir so that it does not include the command filename
     */
    *lp3 = 0;

    pGroup = (PGROUP)GetWindowLongPtr(hwndGroup, GWLP_PGROUP);
    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

    if (!GroupCheck(pGroup)) {
    	MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_GROUPRO,
                     (LPTSTR) PTR(lpgd, lpgd->pName),
                     MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
	GlobalUnlock(pGroup->hGroup);
        return NULL;
    }
    GlobalUnlock(pGroup->hGroup);

    if (*lpIconPath) {
	    bNoIconPath = FALSE;
    }

    lstrcpy(szIconExe, lpIconPath);
    if (bNoIconPath && !(dwFlags & CI_NO_ASSOCIATION)) {
        lstrcpy(szIconExe, lpCommand);
    }
    DoEnvironmentSubst(szIconExe, (WORD)CharSizeOf(szIconExe));
    StripArgs(szIconExe);
    if (!bNoIconPath) {
        TagExtension(szIconExe, sizeof(szIconExe));
    }

    if (*szIconExe == TEXT('"') && *(szIconExe + lstrlen(szIconExe)-1) == TEXT('"')) {
        SheRemoveQuotes(szIconExe);
    }

    if (bNoIconPath) {
        //
        // if it's a relative path, extractassociatedicon and LoadLibrary don't
        // handle that so find the executable first
        //
        SetCurrentDirectory(szOriginalDirectory);
        FindExecutable(szIconExe, lpDefDir, szTemp);
        if (*szTemp) {
            lstrcpy(szIconExe, szTemp);
            TagExtension(szIconExe, sizeof(szIconExe));
            if (*szIconExe == TEXT('"') && *(szIconExe + lstrlen(szIconExe)-1) == TEXT('"')) {
		        SheRemoveQuotes(szIconExe);
	        }
        }
        else {
            *szIconExe = 0;    // Use a dummy value so no icons will be found
                               // and progman's item icon will be used instead
                               // This is to make moricons.dll item icon be the
                               // right one.  -johannec 6/4/93
        }
        //
        // reset the current directory to progman's working directory i.e. Windows directory
        //
        SetCurrentDirectory(szWindowsDirectory);

        wIconId = 0;
        wIconIndex = 0;
    }


NoIcon:

    if (!wIconId) {
        TCHAR szOldIconExe[MAX_PATH];

        lstrcpy(szOldIconExe, szIconExe);
        hIcon = ExtractAssociatedIconEx(hAppInstance, szIconExe, &wIconIndex, &wIconId);
        if (lstrcmp(szOldIconExe, szIconExe)) {
            /* using default icon from Progman.exe */
            fUseDefaultIcon = TRUE;
        }
        if (hIcon)
            DestroyIcon(hIcon);
    }

    lpIconRes = NULL;
    hIconRes = NULL;
    if (hModule = LoadLibrary(szIconExe)) {
        fWin32App = TRUE;
        hIconRes = FindResource(hModule, (LPTSTR) MAKEINTRESOURCE(wIconId), (LPTSTR) MAKEINTRESOURCE(RT_ICON));
        if (hIconRes) {
            //dwVer = 0x00030000;  // resource version is windows 3.x
            wVer = 3;  // resource version is windows 3.x
            cbIconRes = (WORD)SizeofResource(hModule, hIconRes);
            hIconRes = LoadResource(hModule, hIconRes);
            lpIconRes = LockResource(hIconRes);
        }
        if (fUseDefaultIcon) {
            wIconId = 0;
        }
    }
    else { // Win 3.1 app

        if (wVer = ExtractIconResInfo(hAppInstance, szIconExe, wIconIndex, &cbIconRes, &hIconRes)){
            lpIconRes = GlobalLock(hIconRes);
        }
    }

    if (!lpIconRes) {
       wIconId = 0;
       wIconIndex = 0;

       // ToddB: I see no harm in always setting the current directory to the WinDir
       //   before jumping back to NoIcon.  Seems to be required to fix a Japanese bug.
       //   The WinDir is the default directory of Progman anyhow, I really don't see
       //   where it's possible for us to not already be in this directory.
       SetCurrentDirectory(szWindowsDirectory);

       goto NoIcon;
    }

    if (!KeepGroupAround(hwndGroup, TRUE)) {
        goto FreeIcon;
    }

    id = FindFreeItemIndex(hwndGroup);
    if (id == 0xFFFF) {
        goto FreeIcon;
    }

    if (id >= CITEMSMAX) {                      // check group size limit
        idError = IDS_TOOMANYITEMS;
        goto FreeIcon;
    }

    offset = AddThing(pGroup->hGroup, (TCHAR)0, (WORD)sizeof(ITEMDEF));
    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);

    if (!offset) {
        goto QuitThis;
    }

    lpgd->rgiItems[id] = offset;
    lpid = ITEM(lpgd, id);

    GlobalUnlock(pGroup->hGroup);
    offset = AddThing(pGroup->hGroup, lpTitle, (WORD)0);
    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
    lpid = ITEM(lpgd, id);
    if (!offset) {
        goto PuntCreation;
    }

    lpid->pName = offset;

    GlobalUnlock(pGroup->hGroup);
    offset = AddThing(pGroup->hGroup, szCommand,(WORD) 0);
    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
    lpid = ITEM(lpgd, id);
    if (!offset) {
        goto PuntCreation;
    }
    lpid->pCommand = offset;

    GlobalUnlock(pGroup->hGroup);
    CheckEscapes(szIconExe, CharSizeOf(szIconExe));
    offset = AddThing(pGroup->hGroup, szIconExe,(WORD) 0);
    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
    lpid = ITEM(lpgd, id);
    if (!offset)
        goto PuntCreation;
    lpid->pIconPath = offset;

    GlobalUnlock(pGroup->hGroup);
    offset = AddThing(pGroup->hGroup, lpIconRes, cbIconRes);
    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
    lpid = ITEM(lpgd, id);
    if (!offset)
        goto PuntCreation;
    lpid->pIconRes = offset;

    if (lppt) {
        lpid->pt = *lppt;
    } else {
        lpid->pt.x = lpid->pt.y = -1;
    }

    lpid->iIcon = wIconId;
    lpid->wIconIndex = wIconIndex;
    //lpid->dwIconVer = dwVer;
    lpid->wIconVer = wVer;
    lpid->cbIconRes = cbIconRes;

    if (cbIconRes != 0xFFFF)
    if (fWin32App) {
        UnlockResource(hIconRes);
        FreeResource(hIconRes);
        FreeLibrary(hModule);
    }
    else {
        GlobalUnlock(hIconRes);
        GlobalFree(hIconRes);
    }
    GlobalUnlock(pGroup->hGroup);

    if (wHotKey) {
        AddTag(pGroup->hGroup, (int)id, (WORD)ID_HOTKEY, (LPTSTR)&wHotKey, sizeof(wHotKey));
    }
    if (fMinimize) {
        AddTag(pGroup->hGroup, (int)id, (WORD)ID_MINIMIZE, NULL, 0);
    }
    if (dwFlags & CI_SEPARATE_VDM) {
        AddTag(pGroup->hGroup, (int)id, (WORD)ID_NEWVDM, NULL, 0);
    }
    if (*szExeDir) {
        AddTag(pGroup->hGroup, (int)id, (WORD)ID_APPLICATIONDIR, szExeDir, 0);
    }

    pItem = LoadItem(hwndGroup, id, dwFlags & CI_ACTIVATE);

    lpgd = (LPGROUPDEF)GlobalLock(pGroup->hGroup);
    lpid = ITEM(lpgd, id);

    if (!pItem) {
        goto PuntCreation;
    }

    lpid->pt.x = pItem->rcIcon.left;
    lpid->pt.y = pItem->rcIcon.top;

    GlobalUnlock(pGroup->hGroup);

#if 0
// DOS apps are no longer set to fullscreen by default in progman
//  5-3-93 johannec (bug 8343)
#ifdef i386
    //
    // If this is a new DOS application, set the default to full screen.
    // This is only done for x86, since mips doesn't have full screen.
    //
    lstrcpy(szCommand, lpCommand);
    DoEnvironmentSubst(szCommand, (WORD)lstrlen(szCommand));
    StripArgs(szCommand);
    TagExtension(szCommand, sizeof(szCommand));
    *szTemp = 0;
    FindExecutable(szCommand, lpDefDir, szTemp);

    if ((dwFlags & CI_SET_DOS_FULLSCRN) && *szTemp && IsDOSApplication(szTemp)) {
        SetDOSApplicationToFullScreen(lpTitle);
    }
#endif
#endif

    KeepGroupAround(hwndGroup, FALSE);

    // We need to save the current group to disk now
    // in case a setup program is doing DDE with us,
    // and they reboot the system when finished.

    if (!SaveGroup (hwndGroup, FALSE)) {
        idError = 0;
        DeleteItem(pGroup, pItem);
        goto FreeIcon;
    }

    return pItem;

PuntCreation:
    /*
     * Note, must set lpid after each because it may move
     */
    DeleteThing(lpgd, (LPDWORD)&lpid->pName, 0);
    DeleteThing(lpgd, (LPDWORD)&lpid->pCommand, 0);
    DeleteThing(lpgd, (LPDWORD)&lpid->pIconPath, 0);
    DeleteThing(lpgd, (LPDWORD)&lpid->pIconRes, lpid->cbIconRes);
    DeleteThing(lpgd, (LPDWORD)&lpgd->rgiItems[id], sizeof(ITEMDEF));

QuitThis:
    cb = SizeofGroup(lpgd);
    UnlockGroup(pGroup->hwnd);

    CheckBeforeReAlloc(pGroup->hGroup);
    GlobalReAlloc(pGroup->hGroup, cb, GMEM_MOVEABLE);

    KeepGroupAround(hwndGroup, FALSE);

FreeIcon:
    if (cbIconRes != 0xFFFF)
    if (fWin32App) {
        UnlockResource(hIconRes);
        FreeResource(hIconRes);
        FreeLibrary(hModule);
    }
    else {
        GlobalUnlock(hIconRes);
        GlobalFree(hIconRes);
    }

    if (idError != 0)
        MyMessageBox(hwndProgman, IDS_GROUPFILEERR, idError, NULL,
                        MB_OK | MB_ICONEXCLAMATION);
    // Force re-read of group.
    //GlobalDiscard(pGroup->hGroup);
    //pGroup->fLoaded = FALSE ;
    //LockGroup(pGroup->hwnd);
    //UnlockGroup(pGroup->hwnd);

    return NULL;
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  DeleteItem() -                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID FAR PASCAL DeleteItem(PGROUP pGroup, PITEM pItem)
{
  LPGROUPDEF lpgd;
  LPITEMDEF  lpid;
  DWORD      cb;
  LPPMTAG    lptag;

  lpgd = LockGroup(pGroup->hwnd);
  if (!lpgd)
      return;

  if (!GroupCheck(pGroup)) {
      MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_GROUPRO,
                   (LPTSTR) PTR(lpgd, lpgd->pName),
                   MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
      InvalidateIcon(pGroup, pItem);
      return;
  }

  NukeIconBitmap(pGroup);

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

  if ( (lpgd->cbGroup != (DWORD)MyDwordAlign((int)lpgd->cbGroup)) ||
       (lpid->pName != (DWORD)MyDwordAlign((int)lpid->pName)) ||
       (lpid->pCommand != (DWORD)MyDwordAlign((int)lpid->pCommand)) ||
       (lpid->pIconPath != (DWORD)MyDwordAlign((int)lpid->pIconPath)) ||
       (lpgd->rgiItems[pItem->iItem] != (DWORD)MyDwordAlign((int)lpgd->rgiItems[pItem->iItem])) ) {

      MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_BADFILE,
                   (LPTSTR) PTR(lpgd, lpgd->pName),
                   MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
      return;
  }


  /* note, must set lpid after each because it may move
   */
  DeleteThing(lpgd, (LPDWORD)&lpid->pName, 0);
  DeleteThing(lpgd, (LPDWORD)&lpid->pCommand, 0);
  DeleteThing(lpgd, (LPDWORD)&lpid->pIconPath, 0);
  DeleteThing(lpgd, (LPDWORD)&lpid->pIconRes, lpid->cbIconRes);
  DeleteThing(lpgd, (LPDWORD)&lpgd->rgiItems[pItem->iItem], sizeof(ITEMDEF));

  while (lptag = FindTag(lpgd,pItem->iItem,0)) {
      /* delete all tags associated with this item
       */
      UnlockGroup(pGroup->hwnd);
      DeleteTag(pGroup->hGroup, lptag->wItem, lptag->wID);
      lpgd = LockGroup(pGroup->hwnd);
  }

  /* Don't need Item anymore so delete it. */
  RemoveItemFromList(pGroup, pItem);

  cb = SizeofGroup(lpgd);

  UnlockGroup(pGroup->hwnd);

  CheckBeforeReAlloc(pGroup->hGroup);
  GlobalReAlloc(pGroup->hGroup, cb, GMEM_MOVEABLE);

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

}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CreateItemIcons() -                                                     */
/*                                                                          */
/* Creates all the item windows...                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID PASCAL CreateItemIcons(HWND hwndGroup)
{
    LPGROUPDEF lpgd;
    int        i;

    lpgd = LockGroup(hwndGroup);

    if (!lpgd) {
        return;
    }

    /*
     * Create the items in reverse Z-Order.
     */
    for (i = lpgd->cItems - 1; i >= 0; i--) {
        if (lpgd->rgiItems[i]) {
            LoadItem(hwndGroup, (WORD)i, TRUE);
        }
    }

    UnlockGroup(hwndGroup);

  // REVIEW This may be not be needed because LoadGroupWindow does a
  // SetInternalWindowPos which MIGHT already be generating the messages
  // to do this.
  if (bAutoArrange && !bAutoArranging)
      ArrangeItems(hwndGroup);
  else if (!bAutoArranging)
      CalcGroupScrolls(hwndGroup);
}



/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CheckIconResolution() -                                                 */
/*                                                                          */
/* Makes sure we have the right icons loaded... reextracts and saves        */
/* the file if not.                                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID NEAR PASCAL CheckIconResolution(HWND hwndGroup)

{
  LPGROUPDEF        lpgd;
  LPITEMDEF         lpid;
  register PGROUP   pGroup;
  HANDLE            hGroup;
  BOOL              fGottaDoIt;
  register HDC      hdc;
  int               i;
  HICON             hIcon;
  WORD              cbIconRes;
  DWORD             pIconRes;
  LPTSTR            lpIconRes;
  WORD              wFormat;
  TCHAR             szTemp[MAXITEMPATHLEN];
  HANDLE            hModule;
  BOOL              fWin32App;
  //DWORD             dwVer;
  WORD              wVer;

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

  hdc = GetDC(hwndGroup);

  wFormat = (WORD)GetDeviceCaps(hdc, BITSPIXEL) |
        (WORD)GetDeviceCaps(hdc, PLANES) * (WORD)256;

  ReleaseDC(hwndGroup,hdc);

  fGottaDoIt = lpgd->wIconFormat != wFormat ||
               lpgd->cxIcon != (WORD)GetSystemMetrics(SM_CXICON) ||
               lpgd->cyIcon != (WORD)GetSystemMetrics(SM_CYICON);

  if (!fGottaDoIt) {
      goto CleanUpAndLeave;
  }

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

  NukeIconBitmap(pGroup);

  /* Save the new resolution parameters in the group file. */
  lpgd->wIconFormat = wFormat;
  lpgd->cxIcon = (WORD)GetSystemMetrics(SM_CXICON);
  lpgd->cyIcon = (WORD)GetSystemMetrics(SM_CYICON);

  hGroup = pGroup->hGroup;

  for (i = 0; i < (int)lpgd->cItems; ++i) {
      if (!lpgd->rgiItems[i])
          continue;

      lpid = ITEM(lpgd, i);
      DeleteThing(lpgd, (LPDWORD)&lpid->pIconRes, lpid->cbIconRes);
      lpid = ITEM(lpgd, i);

      lstrcpy(szTemp, (LPTSTR) PTR(lpgd, lpid->pIconPath));
      if (!*szTemp) {
          /* Get default icon path */
          lstrcpy(szTemp, (LPTSTR) PTR(lpgd, lpid->pCommand));
          DoEnvironmentSubst(szTemp, (WORD)(MAXITEMPATHLEN+1));
          StripArgs(szTemp);
      }
      SheRemoveQuotes(szTemp);
    cbIconRes = 0xFFFF;
    lpIconRes = NULL;
    hIcon = NULL;

    if (hModule = LoadLibrary(szTemp)) {
        // if WIN32 app
        fWin32App = TRUE;
        hIcon = (HICON)FindResource(hModule, (LPTSTR) MAKEINTRESOURCE(lpid->iIcon), (LPTSTR) MAKEINTRESOURCE(RT_ICON));
        if (hIcon) {
            //dwVer = 0x00030000;
            wVer = 3;
            cbIconRes = (WORD)SizeofResource(hModule, (HRSRC)hIcon);
            hIcon = (HICON)LoadResource(hModule, (HRSRC)hIcon);
            lpIconRes = LockResource(hIcon);
        }
    }
    else { // Win 3.1 app
        fWin32App = FALSE;
        if (wVer = ExtractIconResInfo(hAppInstance, szTemp, lpid->iIcon, &cbIconRes, (LPHANDLE)&hIcon)){
            lpIconRes = GlobalLock(hIcon);
        }
    }


      UnlockGroup(hwndGroup);

      pIconRes = AddThing(pGroup->hGroup, lpIconRes, cbIconRes);
      lpgd = LockGroup(hwndGroup);
      if (!lpgd)
          continue;

      /* In case the segment got moved... */
      lpid = ITEM(lpgd, i);

      if (hIcon)
      if (fWin32App) {
          UnlockResource(hIcon);
          FreeResource(hIcon);
          FreeLibrary(hModule);
      }
      else {
          GlobalUnlock(hIcon);
          GlobalFree(hIcon);
      }

      lpid->pIconRes = pIconRes;
      //lpid->dwIconVer = dwVer;
      lpid->wIconVer = wVer;
      lpid->cbIconRes = cbIconRes;
    }

#ifdef ORGCODE
      // Check everythings OK.
      if (!pHdr || !pAND || !pXOR)
        {
          // FU - delete icon stuff for this item..
          // REVIEW UNDONE - warn user about memory problem  ?

#ifdef DEBUG
          KdPrint(("PM.CIR: Corrupted icon %s \n\r", (LPTSTR) PTR(lpid->pName)));
#endif
          lpid = ITEM(lpgd, i);
          DeleteThing(lpgd, (LPDWORD)&lpid->pIconRes, lpid->cbIconRes);

          // Mark item as being effed - the header is checked by
          // GetItemIcon.
          lpid = ITEM(lpgd, i);
          lpid->pIconRes = NULL;
          lpid->wIconVer = 0;           // This is the important one.
          lpid->cbIconRes = 0;

          // Warn user when we're through, not right in the middle.
          fErrorOnExtract = TRUE;
        }
    }
#endif

  if (!GroupCheck(pGroup))
      MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_EEGROUPRO,
                   (LPTSTR) PTR(lpgd, lpgd->pName),
                   MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);


CleanUpAndLeave:
  UnlockGroup(hwndGroup);

//REVIEW See above.
  KeepGroupAround(hwndGroup, FALSE);
  return;
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CreateGroupHandle() -                                                   */
/*                                                                          */
/* Creates a discarded handle for use as a group handle... on the first     */
/* LockGroup() the file will be loaded.                                     */
/*                                                                          */
/*--------------------------------------------------------------------------*/

HANDLE NEAR PASCAL CreateGroupHandle(void)
{
  register HANDLE   hGroup;

  if (hGroup = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 1L))
      GlobalDiscard(hGroup);

  return(hGroup);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  StartupGroup() -                                                        */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID FAR PASCAL StartupGroup(HWND hwnd)
{
  PITEM pItemCur, pItemExec;
  LPGROUPDEF lpgd;
  INT xLast, yLast;    // Coord of icon to exec.
  INT xBest, yBest;    // Coord of next topmost-leftmost icon.
  MSG msg;             // Peek a message.
  PGROUP pGroup;       // The group.

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

  /*
   * Handle Startup group in icon position order - not Z-order.
   */
  lpgd = LockGroup(hwnd);
  if (!lpgd)
      return;

  /*
   * Starts with the top left and works from left to right then
   * top to bottom.
   * This is really naff in terms of speed, but groups are usually
   * small and the time cost is still small compared to that of execing.
   */
  yLast = WORD_MIN;
  xLast = WORD_MIN;
  for (;;) {
      /*
       * Init
       */
      xBest = WORD_MAX;
      yBest = WORD_MAX;
      pItemExec = NULL;
      /*
       * Find next icon to the right of this one.
       */
      for (pItemCur = pGroup->pItems; pItemCur; pItemCur = pItemCur->pNext) {
          /*
           * Look for Icon to the right of this one.
           * REVIEW This will ignore icons stacked on top of each other.
           */
          if (pItemCur->rcIcon.top >= yLast
            && pItemCur->rcIcon.top <= yLast + (cyArrange/2)
            && pItemCur->rcIcon.left < xBest
            && pItemCur->rcIcon.left > xLast) {
              pItemExec = pItemCur;
              xBest = pItemCur->rcIcon.left;
          }
          /*
           * Check if it'll be suitable for the next row.
           */
          else if (pItemCur->rcIcon.top > yLast + (cyArrange/2)
            && pItemCur->rcIcon.top < yBest) {
              yBest = pItemCur->rcIcon.top;
          }

      }

      if (pItemExec) {
          /*
           * Found one on the current row.
           */

          xLast = xBest;
          /*
           * Move this item to the top of the z-order so that any searches
           * done during DDE will find the last execed item first.
           * REVIEW This messes with the z-odrder of the startup group.
           */
          BringItemToTop(pGroup, pItemExec, TRUE);
          /* Start it up. */
          ExecItem(pGroup,pItemExec,FALSE, TRUE);
          /*
           * Handle any DDE before doing anything else to stop
           * the message queue from over-flowing.
           */
          while(PeekMessage(&msg, hwndProgman, 0, 0, PM_REMOVE|PM_NOYIELD)) {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
          }


      }
      else if (yBest != WORD_MAX) {
          /*
           * Nothing left on the current row but there is another row.
           */
          yLast = yBest;
          xLast = WORD_MIN;

      }
      else {
          /*
           * Nothing left.
           */
          goto Quit;
      }
  }

Quit:
  UnlockGroup(pGroup->hwnd);
}


/*---------------------------------------------------------------------------
 * Check for null item pointers and item pointers that go out of the group.
 * REVIEW UNDONE this doesn't check that the pointers point to things
 * after the end of the items array.
 */
BOOL NEAR PASCAL ValidItems(LPGROUPDEF lpgd)
{
    INT i;
    LPITEMDEF lpid;
    DWORD cbGroup;

    if (!lpgd)
        return FALSE;


    cbGroup = lpgd->cbGroup;

    for (i = 0; (WORD)i < lpgd->cItems; i++) {
        if (!lpgd->rgiItems[i])
	        continue;

        lpid = ITEM(lpgd,i);
        if (!lpid)
            return FALSE;

        if (lpid->pName > cbGroup)
            return FALSE;
        if (lpid->pCommand > cbGroup)
            return FALSE;
        if (lpid->pIconPath > cbGroup)
            return FALSE;
        if ((lpid->cbIconRes != (WORD)-1) && (lpid->pIconRes > cbGroup))
            return FALSE;

    }

    return(TRUE);
}



/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  IsGroupAlreadyLoaded() -                                                */
/*                                                                          */
/* Determines if the user is trying to load a currently loaded group.       */
/*                                                                          */
/*--------------------------------------------------------------------------*/

HWND NEAR PASCAL IsGroupAlreadyLoaded(LPTSTR lpGroupKey, BOOL bCommonGroup)
{
  HWND     hwndT;
  PGROUP   pGroup;

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

      pGroup = (PGROUP)GetWindowLongPtr(hwndT, GWLP_PGROUP);
      if (!lstrcmpi(lpGroupKey, pGroup->lpKey)) {

          if (bCommonGroup) {

              if (pGroup->fCommon)
                  return(hwndT);

          } else {

              if (!pGroup->fCommon)
                  return(hwndT);

          }
      }
  }
  return(NULL);
}


BOOL NEAR PASCAL IndexUsed(WORD wIndex, BOOL bCommonGroup)
{
    PGROUP pGroup;

    for (pGroup = pFirstGroup; pGroup; pGroup = pGroup->pNext)
        if ((pGroup->wIndex == wIndex) && (pGroup->fCommon == bCommonGroup))
            return TRUE;

    return FALSE;
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  LoadGroupWindow() -                                                     */
/*                                                                          */
/* Creates a group window by sending an MDI create message to the MDI client. */
/* The MDICREATESTRUCT contains a parameter pointer to the name of the group  */
/* key.                                                                     */
/*                                                                          */
/*--------------------------------------------------------------------------*/
// GroupFile must be ANSI.

HWND PASCAL LoadGroupWindow(LPTSTR lpKey, WORD wIndex, BOOL bCommonGroup)
{
    MDICREATESTRUCT mdics;
    PGROUP          pGroup;
    TCHAR           szGroupClass[64];
    LPGROUPDEF      lpgd;
    HWND            hwnd;
    TCHAR           szCommonGroupSuffix[MAXKEYLEN];
    TCHAR           szCommonGroupTitle[2*MAXKEYLEN];
    WINDOWPLACEMENT wp;

    //
    // Check if the group is already loaded. This will prevent duplicate groups.
    //
    if (hwnd = IsGroupAlreadyLoaded(lpKey, bCommonGroup)) {
        return(hwnd);
    }

    if (!wIndex) {
        while (IndexUsed(++wIndex, bCommonGroup))
                ;
    }

    if (!LoadString(hAppInstance, IDS_GROUPCLASS, szGroupClass,
            CharSizeOf(szGroupClass))) {
        return NULL;
    }

    pGroup = (PGROUP)LocalAlloc(LPTR,sizeof(GROUP));
    if (!pGroup) {
        return NULL;
    }

    pGroup->hGroup = CreateGroupHandle();
    pGroup->pItems = NULL;
    pGroup->hbm    = NULL;
    pGroup->wIndex = wIndex;
    pGroup->fCommon = bCommonGroup;
    pGroup->ftLastWriteTime.dwLowDateTime = 0;
    pGroup->ftLastWriteTime.dwHighDateTime = 0;

    pGroup->lpKey = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR)*(lstrlen(lpKey) + 1));
    pGroup->fLoaded = FALSE;

    if (!pGroup->lpKey) {
GoAway:
        GlobalFree(pGroup->hGroup);
        LocalFree((HANDLE)pGroup);
        if (!fLowMemErrYet) {
            MyMessageBox(hwndProgman, IDS_APPTITLE, IDS_LOWMEMONINIT,
                    lpKey, MB_OK|MB_ICONEXCLAMATION);
            fLowMemErrYet = TRUE;
        }
        return NULL;
    }

    lstrcpy(pGroup->lpKey, lpKey);

    mdics.szTitle = TEXT("");
    mdics.hOwner = hAppInstance;
    mdics.szClass = szGroupClass;
    mdics.style = WS_VSCROLL|WS_HSCROLL;
    mdics.x = mdics.y = mdics.cx = mdics.cy = CW_USEDEFAULT;
    mdics.lParam = (LPARAM)pGroup;

    /*
     * REVIEW HACK - Set the auto arranging flag to stop the group being
     * loaded by ArrangingIcons doing a LockGroup  and then producing
     * an error if something goes wrong. We're going to do a lock
     * later on anyway and we don't want two error messages.
     */
    bAutoArranging = TRUE;
    pGroup->hwnd = (HWND)SendMessage(hwndMDIClient, WM_MDICREATE, 0, (LPARAM)(LPTSTR)&mdics);
    bAutoArranging = FALSE;

    if (!pGroup->hwnd) {
        LocalFree((HANDLE)pGroup->lpKey);
        goto GoAway;
    }

    /*
     * Note that we're about to load a group for the first time.
     * NB Stting this tells LockGroup that the caller can handle the errors.
     */
    fFirstLoad = TRUE;
    lpgd = LockGroup(pGroup->hwnd);
    /*
     * The group has been loaded or at least we tried.
     */
    fFirstLoad = FALSE;
    if (!lpgd) {
LoadFail:
        /* Loading the group failed somehow... */
        SendMessage(hwndMDIClient, WM_MDIDESTROY, (WPARAM)pGroup->hwnd, 0L);
        //
        // stop handling of Program Groups key changes.
        //
        bHandleProgramGroupsEvent = FALSE;
        RegDeleteKey(hkeyProgramGroups, pGroup->lpKey);

        //
        // reset handling of Program Groups key changes.
        //
        ResetProgramGroupsEvent(bCommonGroup);
        bHandleProgramGroupsEvent = TRUE;

        LocalFree((HANDLE)pGroup->lpKey);
        GlobalFree(pGroup->hGroup);
        LocalFree((HANDLE)pGroup);
        return NULL;
    }

    /*
     * test if it is a Windows 3.1 group file format. If so it is not
     * valid in WIN32. In Windows 3.1 RECT and POINT are WORD instead of LONG.
     */

    if ( (lpgd->rcNormal.left != (INT)(SHORT)lpgd->rcNormal.left) ||
         (lpgd->rcNormal.right != (INT)(SHORT)lpgd->rcNormal.right) ||
         (lpgd->rcNormal.top != (INT)(SHORT)lpgd->rcNormal.top) ||
         (lpgd->rcNormal.bottom != (INT)(SHORT)lpgd->rcNormal.bottom) ){
        /* The group is invalid. */
        MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_BADFILE,
                                (LPTSTR) PTR(lpgd, lpgd->pName),
                                MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
        UnlockGroup(pGroup->hwnd);
        goto LoadFail;
    }

    if (!ValidItems(lpgd)) {
        MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_BADFILE,
                                (LPTSTR) PTR(lpgd, lpgd->pName),
                                MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
        UnlockGroup(pGroup->hwnd);
        goto LoadFail;
    }


    if (lpgd->nCmdShow) {
        SetInternalWindowPos(pGroup->hwnd, (UINT)lpgd->nCmdShow, &lpgd->rcNormal,
                &lpgd->ptMin);
    }

    if (pGroup->fCommon) {

        //
        // Add the common group suffix to the name of the group e.g. (Common)
        // Only do this if the group window is not minimized.
        //

        wp.length = sizeof(WINDOWPLACEMENT);
        GetWindowPlacement(pGroup->hwnd, &wp);
        if ((wp.showCmd == SW_MINIMIZE) ||
            (wp.showCmd == SW_SHOWMINIMIZED) ||
            (wp.showCmd == SW_SHOWMINNOACTIVE) ) {
            SetWindowText(pGroup->hwnd, (LPTSTR) PTR(lpgd, lpgd->pName));
        }
        else {
            lstrcpy(szCommonGroupTitle, (LPTSTR) PTR(lpgd, lpgd->pName));
            if (LoadString(hAppInstance, IDS_COMMONGRPSUFFIX, szCommonGroupSuffix,
                           CharSizeOf(szCommonGroupSuffix))) {
                lstrcat(szCommonGroupTitle, szCommonGroupSuffix);
            }
            SetWindowText(pGroup->hwnd, szCommonGroupTitle);
            if (!UserIsAdmin) {
                pGroup->fRO = TRUE;
            }
        }
    }
    else {
        SetWindowText(pGroup->hwnd, (LPTSTR) PTR(lpgd, lpgd->pName));
    }


    UnlockGroup(pGroup->hwnd);

    //CheckIconResolution(pGroup->hwnd);

    CreateItemIcons(pGroup->hwnd);

    /*
     * Link the group.
     */
    pGroup->pNext = NULL;
    *pLastGroup = pGroup;
    pLastGroup = &pGroup->pNext;
    pCurrentGroup = pGroup;

#ifdef NOTINUSER
    CalcChildScroll(pGroup->hwnd, SB_BOTH);
#endif

    GroupCheck(pGroup);

    return(pGroup->hwnd);
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  UnloadGroupWindow() -                                                   */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void FAR PASCAL UnloadGroupWindow(HWND hwnd)
{
    PGROUP pGroup, *ppGroup;
    PITEM pItem, pItemNext;

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

    /* Destroy the window. */
    SendMessage(hwndMDIClient,WM_MDIDESTROY,(WPARAM)hwnd,0L);

    /* Free the group segment. */
    GlobalFree(pGroup->hGroup);

    /* Free the local stuff. */
    LocalFree((HANDLE)pGroup->lpKey);

    /* The cached bitmap if there is one. */
    if (pGroup->hbm) {
        DeleteObject(pGroup->hbm);
        pGroup->hbm = NULL;
    }

    /* The item data. */
    for (pItem = pGroup->pItems; pItem; pItem = pItemNext) {
        pItemNext = pItem->pNext;
        LocalFree((HANDLE) pItem);
    }

    /* Remove the group from the linked list. */
    for (ppGroup = &pFirstGroup; *ppGroup; ppGroup = &((*ppGroup)->pNext)) {
        if (*ppGroup == pGroup) {
            *ppGroup = pGroup->pNext;
            break;
        }
    }
    if (pLastGroup == &pGroup->pNext)
	pLastGroup = ppGroup;

    /* Lastly, free the group structure itself. */
    LocalFree((HANDLE)pGroup);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  RemoveBackslashFromKeyName() -                                          */
/*                                                                          */
/*  replace the invalid characters for a key name by some valid charater.   */
/*  the same characters that are invalid for a file name are invalid for a  */
/*  key name.                                                               */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void RemoveBackslashFromKeyName(LPTSTR lpKeyName)
{
    LPTSTR lpt;

    for (lpt = lpKeyName; *lpt; lpt++) {
        if ((*lpt == TEXT('\\')) || (*lpt == TEXT(':')) || (*lpt == TEXT('>')) || (*lpt == TEXT('<')) ||
             (*lpt == TEXT('*')) || (*lpt == TEXT('?')) ){
            *lpt = TEXT('.');
        }
    }
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  CreateNewGroup() -                                                      */
/*                                                                          */
/*  This function creates a new, empty group.                               */
/*                                                                          */
/*--------------------------------------------------------------------------*/

HWND PASCAL CreateNewGroup(LPTSTR pGroupName, BOOL bCommonGroup)
{
    HANDLE      hT;
    LPGROUPDEF  lpgd;
    PGROUP      pGroup;
    HDC         hdc;
    int         i;
    int         cb;
    TCHAR       szKeyName[MAXKEYLEN+1];
    HWND        hwnd;
    DWORD       status = 0;
    WORD        cGroups;
    INT         wGroupNameLen;   //length of pGroupName DWORD aligned.
    HKEY        hkeyGroups;
    HKEY        hKey;
    HWND        hwndT;
    PSECURITY_ATTRIBUTES pSecAttr;

    /*
     * Check we're not trying to create too many groups.
     * Count the current number of groups.
     */
    cGroups = 0;

    for (hwnd = GetWindow(hwndMDIClient, GW_CHILD); hwnd; hwnd = GetWindow(hwnd, GW_HWNDNEXT)) {
        if (GetWindow(hwnd, GW_OWNER))
            continue;
        //
        // count the common groups seperately from the personal group.
        // Both have a maximum of CGROUPSMAX groups.
        //
        pGroup = (PGROUP)GetWindowLongPtr(hwnd, GWLP_PGROUP);
        if (bCommonGroup && pGroup->fCommon ||
                             !bCommonGroup && !pGroup->fCommon) {
            cGroups++;
        }
    }

    // Compare with limit.
    if (cGroups >= CGROUPSMAX) {
        status = bCommonGroup ? IDS_TOOMANYCOMMONGROUPS : IDS_TOOMANYGROUPS;
        goto Exit;
    }

    if (bCommonGroup) {

        hkeyGroups = hkeyCommonGroups;
        pSecAttr = pAdminSecAttr;
        if (!hkeyGroups) {
            if (MyMessageBox(hwndProgman,
                             IDS_COMMONGROUPERR,
                             IDS_NOCOMMONGRPS,
                             pGroupName,
                             MB_OKCANCEL | MB_ICONEXCLAMATION | MB_TASKMODAL)
                       == IDOK) {

                hkeyGroups = hkeyProgramGroups;
                pSecAttr = pSecurityAttributes;
                bCommonGroup = FALSE;

            } else {
                return(NULL);
            }
        }

    } else {

        hkeyGroups = hkeyProgramGroups;
        pSecAttr = pSecurityAttributes;

    }

    if (!hkeyGroups) {
        status = IDS_NOGRPFILE;
        goto Exit;
    }

    //
    // Replace backslash in the group name because the registry does not
    // allow key names with backslash, bckslash is used to separate keys.
    //
    lstrcpy(szKeyName, pGroupName);
    RemoveBackslashFromKeyName(szKeyName);

    //
    // Test for existing key.
    //
    while (!RegOpenKeyEx(hkeyGroups, szKeyName, 0, KEY_READ, &hKey)) {
        /* a group with this name already exists */
        if (hwndT = IsGroupAlreadyLoaded(szKeyName, bCommonGroup)) {
            if (lstrlen(szKeyName) < MAXKEYLEN) {
                lstrcat(szKeyName, TEXT("."));
                GlobalUnlock(pGroup->hGroup);
                RegCloseKey(hKey);
                continue;
            }
        }
        RegCloseKey(hKey);
        goto LoadGroupFile;
    }

    wGroupNameLen = MyDwordAlign(sizeof(TCHAR)*(lstrlen(pGroupName) + 1));
    cb = sizeof(GROUPDEF) + (NSLOTS * sizeof(DWORD)) +  wGroupNameLen;

    /*
     * In CreateNewGroup before GlobalAlloc.
     */
    hT = GlobalAlloc(GHND, (DWORD)cb);
    if (!hT) {
        status = IDS_LOWMEM;
        goto Exit;
    }

    lpgd = (LPGROUPDEF)GlobalLock(hT);

    lpgd->dwMagic = GROUP_UNICODE;
    lpgd->cbGroup = (DWORD)cb;
    lpgd->nCmdShow = 0;            /* use MDI defaults  */
    lpgd->pName = sizeof(GROUPDEF) + NSLOTS * sizeof(DWORD);
    hdc = GetDC(NULL);
    lpgd->wIconFormat = (WORD)GetDeviceCaps(hdc, BITSPIXEL) + (WORD)256 *
            (WORD)GetDeviceCaps(hdc, PLANES);
    ReleaseDC(NULL, hdc);
    lpgd->cxIcon = (WORD)GetSystemMetrics(SM_CXICON);
    lpgd->cyIcon = (WORD)GetSystemMetrics(SM_CYICON);
    lpgd->Reserved1 = (WORD)-1;
    lpgd->Reserved2 = (DWORD)-1;

    lpgd->cItems = NSLOTS;

    for (i = 0; i < NSLOTS; i++) {
        lpgd->rgiItems[i] = 0;
    }

    lstrcpy((LPTSTR)((LPSTR)lpgd + sizeof(GROUPDEF) + NSLOTS * sizeof(DWORD)),
            pGroupName);

    /*
     * In CreateNewGroup before SizeofGroup.
     */
    cb = (int)SizeofGroup(lpgd);


    //
    // stop handling of Program Groups key changes when creating groups.
    //
    bHandleProgramGroupsEvent = FALSE;

    //
    // REARCHITECT: pSecurityAttributes might change for Common groups.
    //

    if (!RegCreateKeyEx(hkeyGroups, szKeyName, 0, 0, 0,
                     DELETE | KEY_READ | KEY_WRITE,
                     pSecAttr, &hKey, NULL)) {
        if (RegSetValueEx(hKey, NULL, 0, REG_BINARY, (LPBYTE)lpgd, cb))
            status = IDS_CANTWRITEGRP;
        RegCloseKey(hKey);
    }
    else
        status = IDS_NOGRPFILE;

    GlobalUnlock(hT);
    GlobalFree(hT);

Exit:
    if (status) {
        MyMessageBox(hwndProgman, IDS_GROUPFILEERR, (WORD)status, pGroupName,
                MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
        ResetProgramGroupsEvent(bCommonGroup);
	bHandleProgramGroupsEvent = TRUE;
        return NULL;
    }

LoadGroupFile:
    /*
     * The group file now exists on the disk... load it in!
     */
    fLowMemErrYet = FALSE;
    fErrorOnExtract = FALSE;
    hwnd = LoadGroupWindow(szKeyName, 0, bCommonGroup);

    if (fErrorOnExtract) {
        // On observed problem with icon extraction has been to do
        // with a low memory.
        MyMessageBox(hwndProgman, IDS_OOMEXITTITLE, IDS_LOWMEMONEXTRACT,
            NULL, MB_OK|MB_ICONHAND|MB_SYSTEMMODAL);
    }

    // Save the group section even if SaveSettings is off to stop
    // users from themselves.
    if (!bCommonGroup) {
        WriteGroupsSection();
    }
    ResetProgramGroupsEvent(bCommonGroup);
    bHandleProgramGroupsEvent = TRUE;
    return hwnd;
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  DeleteGroup() -                                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID FAR PASCAL DeleteGroup(HWND hwndGroup)
{
  PGROUP pGroup = (PGROUP)GetWindowLongPtr(hwndGroup, GWLP_PGROUP);
  PGROUP *ppGroup;
  PITEM  pItem;
  TCHAR   szT[10];
  BOOL   bCommonGroup;
  HKEY   hkeyGroups;

  //
  // stop handling of Program Groups key changes when deleting groups.
  //
  bHandleProgramGroupsEvent = FALSE;

  bCommonGroup = pGroup->fCommon;
  if (bCommonGroup)
      hkeyGroups = hkeyCommonGroups;
  else
      hkeyGroups = hkeyProgramGroups;

  if (pGroup->fRO || RegDeleteKey(hkeyGroups, pGroup->lpKey) != ERROR_SUCCESS) {
      MyMessageBox(hwndProgman, IDS_GROUPFILEERR, IDS_ERRORDELETEGROUP,
                                                      pGroup->lpKey, MB_OK);
      //
      // reset handling of Program Groups key changes.
      //
      ResetProgramGroupsEvent(bCommonGroup);
      bHandleProgramGroupsEvent = TRUE;
      return;   // cannot delete the group
  }

  //
  // reset handling of Program Groups key changes.
  //
  ResetProgramGroupsEvent(bCommonGroup);
  bHandleProgramGroupsEvent = TRUE;

  /* Destroy the window, the global memory block, and the file. */
  SendMessage(hwndMDIClient, WM_MDIDESTROY, (WPARAM)hwndGroup, 0L);
  NukeIconBitmap(pGroup);
  GlobalFree(pGroup->hGroup);

  if (!bCommonGroup) {

      //
      // Remove the program manager's settings for that personal group.
      //

      wsprintf(szT,TEXT("Group%d"),pGroup->wIndex);
      RegDeleteValue(hkeyPMGroups, szT);
  }

  /* Unlink the group structure. */
  for (ppGroup=&pFirstGroup; *ppGroup && *ppGroup != pGroup; ppGroup = &(*ppGroup)->pNext)
      ;

  if (*ppGroup)
      *ppGroup = pGroup->pNext;

  if (pLastGroup == &pGroup->pNext)
      pLastGroup = ppGroup;

  /* Destroying the window should activate another one, but if it is the
   * last one, nothing will get activated, so to make sure punt the
   * current group pointer...
   */
  if (pCurrentGroup == pGroup)
      pCurrentGroup = NULL;

  /* Lastly, toss out the group and item structures. */
  while (pGroup->pItems) {
      pItem = pGroup->pItems;
      pGroup->pItems = pItem->pNext;
      LocalFree((HANDLE)pItem);
  }
  LocalFree((HANDLE)pGroup->lpKey);
  LocalFree((HANDLE)pGroup);

  if (!bCommonGroup) {

      //
      // Change the program manager's settings for that personal group.
      //

      WriteGroupsSection();
  }
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  ChangeGroupTitle() -                                                    */
/*                                                                          */
/*  Modifies the name of a program group, on the screen and in the file.    */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID FAR PASCAL ChangeGroupTitle(HWND hwndGroup, LPTSTR lpName, BOOL bCommonGroup)
{
  LPGROUPDEF lpgd;
  PGROUP pGroup;
  DWORD pName;
  TCHAR szCommonGroupSuffix[MAXKEYLEN];
  TCHAR szCommonGroupTitle[2*MAXKEYLEN];
  WINDOWPLACEMENT wp;

  if (!hwndGroup)
      return;

  //
  // Change the title of the window.
  //

  if (bCommonGroup) {

      //
      // Add the common group suffix to the name of the group e.g. (Common),
      // do not append the common suffix if the group window is minimized.
      //
      wp.length = sizeof(WINDOWPLACEMENT);
      GetWindowPlacement(hwndGroup, &wp);
      if (wp.showCmd == SW_MINIMIZE || wp.showCmd == SW_SHOWMINIMIZED ||
          wp.showCmd == SW_SHOWMINNOACTIVE) {
          SetWindowText(hwndGroup, lpName);
      }
      else {

          lstrcpy(szCommonGroupTitle, lpName);
          if (LoadString(hAppInstance, IDS_COMMONGRPSUFFIX, szCommonGroupSuffix,
                         CharSizeOf(szCommonGroupSuffix))) {
              lstrcat(szCommonGroupTitle, szCommonGroupSuffix);
          }
          SetWindowText(hwndGroup, szCommonGroupTitle);
      }
  }
  else {
      SetWindowText(hwndGroup, lpName);
  }

  //
  // Remove the old name.
  //

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

  DeleteThing(lpgd, (LPDWORD)&lpgd->pName, 0);
  UnlockGroup(hwndGroup);

  //
  // Insert the new one.
  //

  pGroup = (PGROUP)GetWindowLongPtr(hwndGroup,GWLP_PGROUP);
  pName = AddThing(pGroup->hGroup, lpName ,(WORD)0);

  //
  // Set the new offset...
  //

  if (lpgd = LockGroup(hwndGroup)) {
      lpgd->pName = pName;
      UnlockGroup(hwndGroup);
  }

}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  SetGroupDimensions() -                                                  */
/*                                                                          */
/*  Saves the size and position of the group file in the group segment, as  */
/*  as well as the positions of all the item icons                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

VOID NEAR PASCAL SetGroupDimensions(HWND hwndGroup)
{
  LPGROUPDEF        lpgd;
  LPITEMDEF        lpid;
  PGROUP pGroup;
  PITEM pItem;
  WORD i;

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

  lpgd->nCmdShow = (WORD)GetInternalWindowPos(hwndGroup, &lpgd->rcNormal,
            &lpgd->ptMin);

  pGroup = (PGROUP)GetWindowLongPtr(hwndGroup,GWLP_PGROUP);
  NukeIconBitmap(pGroup);	// invalidate the bitmap

  for (pItem=pGroup->pItems; pItem; pItem=pItem->pNext) {
      lpid = ITEM(lpgd,pItem->iItem);
      lpid->pt.x = pItem->rcIcon.left;
      lpid->pt.y = pItem->rcIcon.top;

      /* save offset of ITEMDEF for each item
       */
      ChangeTagID(lpgd,pItem->iItem,(int)lpgd->rgiItems[pItem->iItem]);
      pItem->iItem = (int)lpgd->rgiItems[pItem->iItem];
  }

  for (i=0, pItem=pGroup->pItems; pItem; pItem=pItem->pNext, i++) {
      /* write offsets back out in Z order and update the index
       */
      ChangeTagID(lpgd,pItem->iItem,(int)i);
      lpgd->rgiItems[i] = (DWORD)pItem->iItem;
      pItem->iItem = (int)i;
  }

  /* Clear out remaining pointers to prevent duped item wierdness. */
  while (i < lpgd->cItems)
      lpgd->rgiItems[i++] = 0;

  UnlockGroup(hwndGroup);
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  WriteGroupsSection() -                                                  */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void PASCAL WriteGroupsSection(VOID)
{
    PGROUP      pGroup;
    HCURSOR     hCursor;
    HWND        hwndGroup;
    LPGROUPDEF  lpgd;
    TCHAR        szT[66];
    TCHAR        szOrd[CGROUPSMAX*8+7];
    TCHAR szFmt[] = TEXT("Group%d");
    TCHAR szFmtCommonGrp[] = TEXT("GroupC%d");
    INT cGroups;
    TCHAR szGroupKey[MAXKEYLEN];
    INT i;
    RECT rc;
    POINT ptMin;
    TCHAR szCGrpInfo[MAXKEYLEN];
    INT cbValueName;

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

    if (!(hwndGroup = GetWindow(hwndMDIClient, GW_CHILD))) {
        goto WPIExit;
    }

    hwndGroup = GetWindow(hwndGroup, GW_HWNDLAST);

    szOrd[0] = 0;
    cGroups = 0;

    //
    // Clear user's previous positioning of common groups.
    //
    if (hkeyPMCommonGroups) {
        cbValueName = CharSizeOf(szGroupKey);
        while (!RegEnumValue(hkeyPMCommonGroups, 0, szGroupKey, &cbValueName, 0, 0,
                             0, 0)) {
            RegDeleteValue(hkeyPMCommonGroups, szGroupKey);
            cbValueName = CharSizeOf(szGroupKey);
        }
    }

    for (; hwndGroup; hwndGroup = GetWindow(hwndGroup, GW_HWNDPREV)) {
        /*
         * Check to make sure we're not out of room for the order string.
         */
        if (cGroups > CGROUPSMAX) {
            MessageBeep(0);
            break;
        }

        if (GetWindow(hwndGroup, GW_OWNER)) {
            continue;
        }

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

        if (!pGroup->lpKey || !*pGroup->lpKey) {
            if (pGroup->lpKey) {
                LocalFree((HANDLE)pGroup->lpKey);
            }

            lpgd = LockGroup(hwndGroup);
            if (!lpgd) {
                continue;
            }

            lstrcpy(szGroupKey, (LPTSTR) PTR(lpgd, lpgd->pName));
            pGroup->lpKey = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR)*(lstrlen(szGroupKey) + 1));
            lstrcpy(pGroup->lpKey, szGroupKey);
            UnlockGroup(hwndGroup);
        }

        if (pGroup->fCommon) {
            wsprintf(szT, szFmtCommonGrp, pGroup->wIndex);
            lstrcat(szOrd, TEXT(" C"));
            lstrcat(szOrd, szT + CCHCOMMONGROUP);
            if (hkeyPMCommonGroups) {
                i = GetInternalWindowPos(hwndGroup, &rc, &ptMin);

                if (i==SW_SHOWMINNOACTIVE)
                    i = SW_SHOWNORMAL;

                wsprintf(szCGrpInfo, TEXT("%d %d %d %d %d %d %d "),
                          rc.left, rc.top, rc.right, rc.bottom,
                          ptMin.x, ptMin.y, i);
                lstrcat(szCGrpInfo, pGroup->lpKey);
                RegSetValueEx(hkeyPMCommonGroups, szT, 0, REG_SZ, (LPBYTE)szCGrpInfo, sizeof(TCHAR)*(lstrlen(szCGrpInfo)+1));
            }
        }
        else {
            wsprintf(szT, szFmt, pGroup->wIndex);
            lstrcat(szOrd, TEXT(" "));
            lstrcat(szOrd, szT + CCHGROUP);
            if (hkeyPMGroups) {
                RegSetValueEx(hkeyPMGroups, szT, 0, REG_SZ, (LPBYTE)pGroup->lpKey, sizeof(TCHAR)*(lstrlen(pGroup->lpKey)+1));
            }
        }

        cGroups++;
    }

    if (hkeyPMSettings) {
        RegSetValueEx(hkeyPMSettings, szOrder, 0, REG_SZ, (LPBYTE)szOrd, sizeof(TCHAR)*(lstrlen(szOrd) + 1));
    }

WPIExit:
    ShowCursor(FALSE);
    SetCursor(hCursor);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  SaveGroupsContent() -
/*
/*  Save the contents of the all the groups, doesn't save changes in
/*  size or position if bSaveGroupSettings is FALSE, only the group items.
/*                                                                          */
/*--------------------------------------------------------------------------*/

BOOL SaveGroupsContent(BOOL bSaveGroupSettings)
{
    HWND hwndGroup;

    hwndGroup = GetWindow(hwndMDIClient, GW_CHILD);
    if (!hwndGroup)
        return FALSE;

    for (hwndGroup=GetWindow(hwndGroup, GW_HWNDLAST); hwndGroup; hwndGroup=GetWindow(hwndGroup, GW_HWNDPREV)) {
        if (GetWindow(hwndGroup, GW_OWNER))
            continue;

        /* Save the latest sizes and positions. */
        if (bSaveGroupSettings) {
            SetGroupDimensions(hwndGroup);
            if (wLockError == LOCK_LOWMEM && !fLowMemErrYet) {
                // No more error messages.
                fLowMemErrYet = TRUE;
                wLockError = 0;
                // Warn user that some settings couldn't be saved.
                MyMessageBox(hwndProgman, IDS_OOMEXITTITLE, IDS_LOWMEMONEXIT, NULL,
                     MB_OK | MB_ICONHAND | MB_SYSTEMMODAL);
            }
        }

        SaveGroup(hwndGroup, TRUE);
    }

    RegFlushKey(HKEY_CURRENT_USER);

    return(TRUE);
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  WriteINIFile() -                                                        */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void FAR PASCAL WriteINIFile()
{
    register int  i;
    RECT          rc;
    TCHAR          szT[40];
    HANDLE        hCursor;

    //
    // Don't save if restricted. But force save if we've just converted the
    // ansi groups to unicode so we can work from unicode the next time around.
    //
    if (fNoSave && !bUseANSIGroups)
        return;

    fLowMemErrYet = FALSE;

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

    i = GetInternalWindowPos(hwndProgman, &rc, NULL);

    if (i==SW_SHOWMINNOACTIVE)
        i = SW_SHOWNORMAL;

    wsprintf(szT, TEXT("%d %d %d %d %d"), rc.left, rc.top, rc.right, rc.bottom, i);
    if (hkeyPMSettings) {
        RegSetValueEx(hkeyPMSettings, szWindow, 0, REG_SZ, (LPBYTE)szT, sizeof(TCHAR)*(lstrlen(szT)+ 1));
    }

    if (!bLoadEvil)
        WriteGroupsSection();
    //
    // Save all groups content, TRUE means we want the size and position saved
    // as well.
    //
    SaveGroupsContent(TRUE);

    ShowCursor(FALSE);
    SetCursor(hCursor);
}




/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  GetItemCommand() -                                                      */
/*                                                                          */
/*--------------------------------------------------------------------------*/

void FAR PASCAL GetItemCommand(
    PGROUP pGroup,
    PITEM pItem,
    LPTSTR lpCommand,
    LPTSTR lpDir)
{
    BYTE b=0;
    LPTSTR lp1, lp2, lp3;
    LPGROUPDEF lpgd;
    LPITEMDEF lpid;
    BOOL bNoFirstQuote = TRUE;

    if (!GetGroupTag(pGroup,pItem,(WORD)ID_APPLICATIONDIR,lpCommand,MAXITEMPATHLEN)) {
        // the application directory is not defined
        *lpCommand = 0;
    }

    lpgd = LockGroup(pGroup->hwnd);
    if (!lpgd) {
        *lpCommand = 0;
        *lpDir = 0;
        return;
    }
    lpid = ITEM(lpgd,pItem->iItem);

    // init working directory
    lp3 = lpDir;
    *lp3 = 0;

    // item command
    lp1 = (LPTSTR) PTR(lpgd, lpid->pCommand);
    if (*lp1 == TEXT('"')) {
        lp2 = lp1;
        while (lp2 && (lp2 = wcschr(lp2+1, TEXT('"'))) && *(lp2+1) != TEXT('\\')) { //go to next quote
             ;
        }
        if (!lp2) {
            //
            // The directory is not in quotes and since the command path starts
            // with a quote, there's no working directory.
            //
            lp2 = lpDir;
	    *lp2 = 0;
        }
        else {
            if (*(lp2+1) == TEXT('\\')) {
                //
                // the working directory is in quotes
                //
                *lp3++ = *lp1++; //write first quote

                for (; *lp1 && lp1 != lp2; lp1 = CharNext(lp1)) {
                    *lp3++ = *lp1;
                }
                if (*lp1 == TEXT('"')) {
                   *lp3++ = *lp1++; //write last quote
                   lp1++;
                   *lp3 = 0;
                }
            }
            lp2 = lp3 + lstrlen(lp3);
	}
    }
    else {
        //
        // if there's a working directory, it is not in quotes
        //

        for (lp2 = lp3 = lpDir; *lp1 && *lp1 != TEXT(' ') && *lp1 != TEXT('"'); // the command line might be in quotes
                                               lp1 = CharNext(lp1)) {

            *lp3++ = *lp1;

            if (*lp1 == TEXT(':') || *lp1 == TEXT('\\'))
                lp2 = lp3;
        }
        *lp3 = 0;
    }

    /* we are assuming the exe dir contains the necessary separator
     * add the filename to the command line
     */
    lstrcat(lpCommand,lp2);
    /* add the arguments to the command line
     */
    lstrcat(lpCommand,lp1);


    /* truncate the command name from the exe path.  note this implies
     * that if there is no path, lpDir will be empty
     */
    *lp2 = 0;
    lp2 = CharPrev(lpDir,lp2);
    if (*lp2 == TEXT('\\') && *CharPrev(lpDir,lp2) != TEXT(':'))
        *lp2 = 0;

    UnlockGroup(pGroup->hwnd);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  DuplicateItem() -                                                       */
/*                                                                          */
/*--------------------------------------------------------------------------*/

PITEM PASCAL DuplicateItem(
    PGROUP pGroup,
    PITEM pItem,
    PGROUP pGNew,
    LPPOINT lppt)
{
    WORD       wIconId;
    WORD       wIconIndex;
    LPITEMDEF  lpid;
    LPGROUPDEF lpgd;
    TCHAR       szCommand[MAXITEMPATHLEN + 1];
    TCHAR       szDefDir[2 * (MAXITEMPATHLEN + 1)];
    TCHAR       szIconPath[MAXITEMPATHLEN + 1];
    TCHAR       szName[64];
    TCHAR       szExpPath[MAXITEMPATHLEN+1];
    TCHAR       szExpDir[MAXITEMPATHLEN+1];
    DWORD       dwFlags = CI_ACTIVATE;

    lpid = LockItem(pGroup, pItem);
    if (lpid == 0L) {
        UnlockGroup(pGroup->hwnd);
        return NULL;
    }

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

    lstrcpy(szName, (LPTSTR) PTR(lpgd, lpid->pName));
    lstrcpy(szIconPath, (LPTSTR) PTR(lpgd, lpid->pIconPath));
    wIconId = lpid->iIcon;
    wIconIndex = lpid->wIconIndex;
    GlobalUnlock(pGroup->hGroup);
    UnlockGroup(pGroup->hwnd);

    GetItemCommand(pGroup, pItem, szCommand, szDefDir);

    //
    // I f there's no icon path, check if we have an executable associated
    // with the command path.
    //
    if (!*szIconPath) {
        lstrcpy(szExpPath, szCommand);
        DoEnvironmentSubst(szExpPath, CharSizeOf(szExpPath));
        StripArgs(szExpPath);
        lstrcpy(szExpDir, szDefDir);
        DoEnvironmentSubst(szExpDir, CharSizeOf(szExpDir));
        FindExecutable(szExpPath, szExpDir, szIconPath);
        if (!*szIconPath) {
            dwFlags |= CI_NO_ASSOCIATION;
        }
        else
            *szIconPath = 0;
    }
    if (GroupFlag(pGroup, pItem, (WORD)ID_NEWVDM)) {
        dwFlags |= CI_SEPARATE_VDM;
    }

    return CreateNewItem(pGNew->hwnd,
                         szName,
                         szCommand,
                         szIconPath,
                         szDefDir,
                         GroupFlag(pGroup, pItem, (WORD)ID_HOTKEY),
                         GroupFlag(pGroup, pItem, (WORD)ID_MINIMIZE),
                         wIconId,
                         wIconIndex,
                         NULL,
                         lppt,
                         dwFlags);
}
