
/***********************************************************************

  $Id$

  archiver.bb2 search, load, save and date parse

  Copyright (c) 1993, 1998 M. Kimes
  Copyright (c) 2004, 2007 Steven H.Levine

  01 Aug 04 SHL Rework lstrip/rstrip usage
  13 Aug 05 SHL Beautify with indent
  13 Aug 05 SHL find_type: correct no sig exists bypass logic
  13 Aug 05 SHL SBoxDlgProc: avoid dereferencing NULL signature
  18 Aug 05 SHL Comments
  31 Dec 05 SHL indent -i2
  08 Dec 05 SHL load_archivers: allow empty startlist
  30 Dec 05 SHL load_archivers: use get_archiver_line?(), clean nits
  29 May 06 SHL SBoxDlgProc: support move, add, delete
  30 May 06 SHL load_archivers: add reload support
  16 Jun 06 SHL load_archivers: support signatures containing 0s
  26 Jun 06 SHL load_archivers: remember where comments are
  14 Jul 06 SHL Use Runtime_Error
  29 Jul 06 SHL Use xfgets, xfgets_bstripcr
  15 Aug 06 SHL Use Runtime_Error more
  01 Nov 06 SHL Turn off leftover debug code
  06 Apr 07 GKY Work around PM DragInfo and DrgFreeDISH limit
  19 Apr 07 SHL Use FreeDragInfoData
  19 Apr 07 SHL Add more drag/drop error checking
  20 Aug 07 GKY Move #pragma alloc_text to end for OpenWatcom compat
  25 Aug 07 SHL load_archivers: add missing close on error path

***********************************************************************/

#define INCL_WIN
#define INCL_WINSTDDRAG
#define INCL_DOS
#define INCL_LONGLONG
#include <os2.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <share.h>
#include <ctype.h>

#include "fm3dll.h"
#include "fm3dlg.h"
#include "fm3str.h"

static PSZ pszSrcFile = __FILE__;

static void free_arc_type(ARC_TYPE * pat);
static void fill_listbox(HWND hwnd, BOOL fShowAll, SHORT sOldSelect);

//=== quick_find_type() ===

ARC_TYPE *quick_find_type(CHAR * filespec, ARC_TYPE * topsig)
{
  ARC_TYPE *info, *found = NULL;
  CHAR *p;

  if (!arcsigsloaded)
    load_archivers();
  p = strrchr(filespec, '.');
  if (p) {
    p++;
    info = (topsig) ? topsig : arcsighead;
    while (info) {
      if (info->ext && *(info->ext) && !stricmp(p, info->ext)) {
	found = find_type(filespec, topsig);
	break;
      }
      info = info->next;
    }
  }
  return found;
}

//=== fill_listbox() fill or refill listbox from current archiver definitions ===

static VOID fill_listbox(HWND hwnd, BOOL fShowAll, SHORT sOldSelect)
{
  ARC_TYPE *pat;
  BOOL found = FALSE;
  SHORT sSelect;

  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_DELETEALL, MPVOID, MPVOID);

  for (pat = arcsighead; pat; pat = pat->next) {
    /*
     * this inner loop tests for a dup signature entry and assures
     * that only the entry at the top of the list gets used for
     * conversion; editing any is okay
     */
    if (!fShowAll) {
      ARC_TYPE *pat2;
      BOOL isDup = FALSE;

      for (pat2 = arcsighead;
	   pat2 && pat->siglen && pat2 != pat && !isDup; pat2 = pat2->next) {
	isDup = pat2->siglen == pat->siglen &&
	  !memcmp(pat2->signature, pat->signature, pat->siglen);
      }					// for
      if (isDup)
	continue;
    }

    // If caller is editing archivers or entry useful to caller, show in listbox
    if (fShowAll || (pat->id && pat->extract && pat->create)) {
      sSelect = (SHORT) WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_INSERTITEM,
					  MPFROM2SHORT(LIT_END, 0),
					  MPFROMP(pat->id ? pat->id : "?"));
      if (!found && *szDefArc && pat->id && !strcmp(szDefArc, pat->id)) {
	// Highlight default
	WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			  MPFROMSHORT(sSelect), MPFROMSHORT(TRUE));
	found = TRUE;
      }
    }
    else {
      // Complain about odd entry
      if (!pat->id || !*pat->id) {
	WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_INSERTITEM,
			  MPFROM2SHORT(LIT_END, 0),
			  MPFROMP(GetPString(IDS_UNKNOWNUNUSABLETEXT)));
      }
      else {
	CHAR s[81];

	sprintf(s, "%0.12s %s", pat->id, GetPString(IDS_UNUSABLETEXT));
	WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_INSERTITEM,
			  MPFROM2SHORT(LIT_END, 0), MPFROMP(s));
      }
    }
  }					// while scanning

  // Try to reselect last selection unless user wants default selection
  if (sOldSelect != LIT_NONE && !found) {
    SHORT sItemCount =
      (SHORT) WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMCOUNT,
				MPVOID, MPVOID);

    if (sOldSelect >= sItemCount)
      sOldSelect = sItemCount - 1;
    if (sOldSelect >= 0) {
      WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			MPFROMSHORT(sOldSelect), MPFROMSHORT(TRUE));
    }
  }

  if (found)
    PosOverOkay(hwnd);
}

ARC_TYPE *find_type(CHAR * filespec, ARC_TYPE * topsig)
{
  HFILE handle;
  ULONG action;
  ULONG len;
  ULONG l;
  ARC_TYPE *info;
  CHAR *p;
  // CHAR buffer[80];
  CHAR buffer[4096];			// 06 Oct 07 SHL Protect against NTFS defect

  if (!arcsigsloaded)
    load_archivers();
  if (!topsig)
    topsig = arcsighead;
  DosError(FERR_DISABLEHARDERR);
  if (DosOpen(filespec,
	      &handle,
	      &action,
	      0,
	      0,
	      OPEN_ACTION_FAIL_IF_NEW |
	      OPEN_ACTION_OPEN_IF_EXISTS,
	      OPEN_FLAGS_FAIL_ON_ERROR |
	      OPEN_FLAGS_NOINHERIT |
	      OPEN_FLAGS_RANDOMSEQUENTIAL |
	      OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY, 0))
    return NULL;
  // Scan signatures
  for (info = topsig; info; info = info->next) {
    if (info->siglen == 0) {
      // No signature -- check extension
      p = strrchr(filespec, '.');
      if (p) {
	p++;
	if (info->ext && *(info->ext) && !stricmp(p, info->ext))
	  break;			// Matched

      }
      continue;				// Next sig

    }
    // Try signature match
    l = info->siglen;
    l = min(l, 79);
    if (!DosChgFilePtr(handle,
		       abs(info->file_offset),
		       (info->file_offset >= 0) ?
		       FILE_BEGIN : FILE_END, &len)) {
      if (!DosRead(handle, buffer, l, &len) && len == l) {
	if (!memcmp(info->signature, buffer, l))
	  break;			// Matched

      }
    }
  }					// for

  DosClose(handle);			/* Either way, we're done for now */
  return info;				/* Return signature, if any */
}

//=== free_arc_type() free allocated ARC_TYPE ===

static void free_arc_type(ARC_TYPE * pat)
{
  if (pat) {
    xfree(pat->id);
    xfree(pat->ext);
    xfree(pat->list);
    xfree(pat->extract);
    xfree(pat->create);
    xfree(pat->move);
    xfree(pat->delete);
    xfree(pat->signature);
    xfree(pat->startlist);
    xfree(pat->endlist);
    xfree(pat->exwdirs);
    xfree(pat->test);
    xfree(pat->createrecurse);
    xfree(pat->createwdirs);
    xfree(pat->movewdirs);
    free(pat);
  }
}

static UINT cur_line_num;	// Input file line counter

//=== get_line_strip_comments() read line, strip comments and whitespace ===

#define ARCHIVER_LINE_BYTES	256

static PSZ get_line_strip_comments(PSZ pszIn, FILE * fp)
{
  PSZ psz = xfgets(pszIn, ARCHIVER_LINE_BYTES, fp, pszSrcFile, __LINE__);
  PSZ psz2;

  if (psz) {
    cur_line_num++;
    psz2 = strchr(pszIn, ';');
    if (psz2)
      *psz2 = 0;			// Chop comment
    bstripcr(pszIn);			// Strip leading white and trailing white and CR/LF

  }
  return psz;
}

//=== get_line_strip_white() read line, strip whitespace ===

static PSZ get_line_strip_white(PSZ pszIn, FILE * fp)
{
  PSZ psz =
    xfgets_bstripcr(pszIn, ARCHIVER_LINE_BYTES, fp, pszSrcFile, __LINE__);

  if (psz)
    cur_line_num++;

  return psz;
}

//=== load_archivers() load or reload archive definitions from archiver.bb2 ===

INT load_archivers(VOID)
{
  FILE *fp;
  CHAR sz[ARCHIVER_LINE_BYTES + 1];
  CHAR *psz;
  ARC_TYPE *pat = NULL;
  ARC_TYPE *patLast = NULL;
  UINT lines_per_arcsig = LINES_PER_ARCSIG;
  UINT per_sig_comment_line_num = 0;
  INT i;

  // Free current signatures
  if (arcsighead) {
    for (pat = arcsighead; pat;) {
      patLast = pat;
      pat = pat->next;
      free_arc_type(patLast);
    }
    arcsighead = NULL;
  }

  arcsigsmodified = FALSE;
  arcsigs_header_lines = 0;
  arcsigs_trailer_line_num = 0;

  DosEnterCritSec();
  psz = searchpath(GetPString(IDS_ARCHIVERBB2));
  if (!psz || !*psz) {
    DosExitCritSec();
    return -1;
  }
  fp = _fsopen(psz, "r", SH_DENYWR);
  DosExitCritSec();
  if (!fp)
    return -2;
  strcpy(archiverbb2, psz);		// Remember full path

  cur_line_num = 0;

  // Line 1 must contain number of lines per signature definition
  if (!get_line_strip_comments(sz, fp)) {
    fclose(fp);
    return -3;
  }
  if (*sz)
    lines_per_arcsig = atoi(sz);
  if (!*sz || lines_per_arcsig < LINES_PER_ARCSIG) {
    fclose(fp);				// 25 Aug 07 SHL
    return -3;
  }

  // Parse rest of file
  // 1st non-blank line starts definition
  // Need to determine header size and start of trailer

  while (!feof(fp)) {
    // If reading header
    if (!arcsigs_header_lines) {
      // Reading header - find header size and start of signtures
      if (!get_line_strip_white(sz, fp))
	break;				// Unexpected EOF
      if (stristr(sz, "-- Current Archivers --")) {
	arcsigs_header_lines = cur_line_num;
	continue;
      }
      if (!*sz || *sz == ';')
	continue;			//  Header comment or blank line
      else {
	// Not a comment, must be start of signatures
	PSZ psz2 = strchr(sz, ';');

	if (psz2) {
	  *psz2 = 0;			// Chop trailing comment
	  bstripcr(sz);			// Strip leading white and trailing white and CR/LF
	}
	arcsigs_header_lines = cur_line_num - 1;
      }
    }
    else {
      // Reading defintiions
      if (!get_line_strip_comments(sz, fp))
	break;				// EOF
    }

    // fixme to avoid allocating empty fields

    // Remember start of per sig comments for next definition
    if (per_sig_comment_line_num == 0)
      per_sig_comment_line_num = cur_line_num;

    if (*sz) {
      // At start of defintion

      pat = xmallocz(sizeof(ARC_TYPE), pszSrcFile, __LINE__);
      if (!pat)
	break;
      pat->id = xstrdup(sz, pszSrcFile, __LINE__);

      pat->comment_line_num = per_sig_comment_line_num;
      pat->defn_line_num = cur_line_num;

      if (!get_line_strip_comments(sz, fp))	// line 2 - extension
	break;
      if (*sz)
	pat->ext = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->ext = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 3 - offset to signature
	break;
      pat->file_offset = atol(sz);
      if (!get_line_strip_comments(sz, fp))	// line 4 - list command
	break;
      if (*sz)
	pat->list = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->list = NULL;
      if (!pat->list)
	break;				// Must have list command - fixme to complain
      if (!get_line_strip_comments(sz, fp))	// line 5
	break;
      if (*sz)
	pat->extract = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->extract = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 6
	break;
      if (*sz)
	pat->exwdirs = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->exwdirs = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 7
	break;
      if (*sz)
	pat->test = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->test = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 8
	break;
      if (*sz)
	pat->create = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->create = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 9
	break;
      if (*sz)
	pat->createwdirs = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->createwdirs = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 10
	break;
      if (*sz)
	pat->createrecurse = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->createrecurse = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 11
	break;
      if (*sz)
	pat->move = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->move = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 12
	break;
      if (*sz)
	pat->movewdirs = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->movewdirs = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 13
	break;
      if (*sz)
	pat->delete = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->delete = NULL;
      if (!get_line_strip_white(sz, fp))	// line 14
	break;
      i = literal(sz);			// Translate \ escapes
      if (i) {
	pat->siglen = i;
	pat->signature = xmalloc(i, pszSrcFile, __LINE__);
	if (!pat->signature)
	  break;
	memcpy(pat->signature, sz, i);	// signature may not be a string
      }
      else {
	pat->siglen = 0;
	pat->signature = NULL;
      }
      if (!get_line_strip_white(sz, fp))	// line 15
	break;
      if (*sz)
	pat->startlist = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->startlist = NULL;
      if (!get_line_strip_white(sz, fp))	// line 16
	break;
      if (*sz)
	pat->endlist = xstrdup(sz, pszSrcFile, __LINE__);
      else
	pat->endlist = NULL;
      if (!get_line_strip_comments(sz, fp))	// line 17
	break;
      pat->osizepos = atoi(sz);
      if (!get_line_strip_comments(sz, fp))	// line 18
	break;
      pat->nsizepos = atoi(sz);
      if (!get_line_strip_comments(sz, fp))	// line 19
	break;
      pat->fdpos = atoi(sz);
      psz = strchr(sz, ',');
      if (psz) {
	psz++;
	pat->datetype = atoi(psz);
      }
      if (!get_line_strip_comments(sz, fp))	// line 20
	break;
      pat->fdflds = atoi(sz);
      if (!get_line_strip_comments(sz, fp))	// line 21
	break;
      pat->fnpos = atoi(sz);
      psz = strchr(sz, ',');
      if (psz) {
	psz++;
	pat->nameislast = (BOOL) (*psz && atol(psz) == 0) ? FALSE : TRUE;
	psz = strchr(psz, ',');
	if (psz) {
	  psz++;
	  pat->nameisnext = (BOOL) (*psz && atol(psz) == 0) ? FALSE : TRUE;
	  psz = strchr(psz, ',');
	  if (psz) {
	    psz++;
	    pat->nameisfirst = (BOOL) (*psz && atol(psz) == 0) ? FALSE : TRUE;
	  }
	}
      }
      // Ignore unknown lines - must be newer file format
      for (i = LINES_PER_ARCSIG; i < lines_per_arcsig; i++) {
	if (!get_line_strip_comments(sz, fp))
	  break;			// Unexpected EOF - fixme to complain
      }

      // Add to list, assume next and prev already NULL
      if (!arcsighead)
	arcsighead = patLast = pat;
      else {
	patLast->next = pat;
	pat->prev = patLast;
	patLast = pat;
      }
      pat = NULL;			// Done with this defintion

      arcsigs_trailer_line_num = cur_line_num + 1;	// In case this is last defintion
      per_sig_comment_line_num = 0;
    }					// if got definition

  }					// while more lines

  fclose(fp);

  free_arc_type(pat);			// In case partial definition in progress

  if (!arcsighead)
    return -4;

  arcsigsloaded = TRUE;

  return 0;
}

#define TEST_DRAG 0			// fixme to be gone or to work

static MRESULT EXPENTRY SDlgListboxSubclassProc(HWND hwnd, ULONG msg,
						MPARAM mp1, MPARAM mp2)
{
  PFNWP pfnOldProc = (PFNWP) WinQueryWindowPtr(hwnd, QWL_USER);

  PDRAGITEM pDItem;
  PDRAGINFO pDInfo;
  BOOL ok;

  static BOOL emphasized = FALSE;
  static PSZ DRMDRF_LBOX = "<DRM_LBOX,DRF_UNKNOWN>";
  static PSZ DRM_LBOX = "DRM_LBOX";

  switch (msg) {
  case WM_BEGINDRAG:
    {
      LONG cur_ndx;
      DRAGITEM ditem;
      DRAGIMAGE dimage;
      HWND hwndDrop;

      // fprintf(stderr, "SDlgListboxSubclassProc: BEGINDRAG\n");
      cur_ndx = WinQueryLboxSelectedItem(hwnd);

      if (cur_ndx != LIT_NONE) {
	pDInfo = DrgAllocDraginfo(1);
	if (pDInfo) {
	  pDInfo->usOperation = DO_DEFAULT;
	  pDInfo->hwndSource = hwnd;

	  memset(&ditem, 0, sizeof(DRAGITEM));
	  ditem.hwndItem = hwnd;
	  ditem.ulItemID = 1;
	  ditem.hstrType = DrgAddStrHandle(DRT_UNKNOWN);
	  ditem.hstrRMF = DrgAddStrHandle(DRMDRF_LBOX);
	  ditem.hstrContainerName = DrgAddStrHandle("");
	  ditem.hstrSourceName = DrgAddStrHandle("");
	  ditem.hstrTargetName = DrgAddStrHandle("");
	  // ditem.fsControl = 0;
	  ditem.fsSupportedOps = DO_MOVEABLE;

	  memset(&dimage, 0, sizeof(DRAGIMAGE));
	  dimage.cb = sizeof(DRAGIMAGE);
	  dimage.hImage = hptrFile;
	  dimage.cptl = 0;
	  dimage.fl = DRG_ICON;
	  dimage.sizlStretch.cx = 32;
	  dimage.sizlStretch.cy = 32;
	  dimage.cxOffset = -16;
	  dimage.cyOffset = 0;
	  DrgSetDragitem(pDInfo, &ditem, sizeof(DRAGITEM), 0);	/* Index of DRAGITEM */
	  hwndDrop = DrgDrag(hwnd, pDInfo, &dimage, 1,	/* One DRAGIMAGE */
			     VK_ENDDRAG, NULL);
	  if (!hwndDrop)
	    Win_Error(hwnd, hwnd, pszSrcFile, __LINE__, "DrgDrag");

	  DrgFreeDraginfo(pDInfo);
	  // WinSetWindowPos(hwnd,HWND_TOP,0,0,0,0,SWP_ACTIVATE);
	}
      }
      break;
    }

  case DM_DRAGOVER:
    ok = FALSE;
    if (!emphasized) {
      POINTL ptl;
      POINTL ptl2;

      emphasized = TRUE;
      ptl.x = SHORT1FROMMP(mp2);
      ptl.y = SHORT2FROMMP(mp2);
      ptl2 = ptl;
      WinMapWindowPoints(HWND_DESKTOP, hwnd, &ptl2, 1);
      // fprintf(stderr, "DRAGOVER mapped x y %d %d to %d %d\n", ptl.x, ptl.y, ptl2.x, ptl2.y);
      WinPostMsg(hwnd, WM_BUTTON1CLICK,
		 MPFROM2SHORT((SHORT) ptl2.x, (SHORT) ptl2.y),
		 MPFROM2SHORT(HT_NORMAL, KC_NONE));
      // fprintf(stderr, "DRAGOVER posted 0x%x WM_BUTTON1CLICK x y %d %d\n", hwnd, ptl2.x, ptl2.y);
    }
    pDInfo = (PDRAGINFO) mp1;		/* Get DRAGINFO pointer */
    if (pDInfo) {
      if (!DrgAccessDraginfo(pDInfo)) {
	Win_Error(HWND_DESKTOP, HWND_DESKTOP, pszSrcFile, __LINE__,
		  "DrgAccessDraginfo");
      }
      else {
	pDItem = DrgQueryDragitemPtr(pDInfo, 0);
	/* Check valid rendering mechanisms and data format */
	ok = DrgVerifyRMF(pDItem, DRM_LBOX, NULL);
	DrgFreeDraginfo(pDInfo);
      }
    }
    return ok ? MRFROM2SHORT(DOR_DROP, DO_MOVE) :
		MRFROM2SHORT(DOR_NEVERDROP, 0);

  case DM_DRAGLEAVE:
    if (emphasized) {
      emphasized = FALSE;
      // fixme to draw listbox item emphasized
      // DrawTargetEmphasis(hwnd, emphasized);
      // fprintf(stderr, "DRAGLEAVE\n");
      fflush(stderr);
    }
    return 0;

  case DM_DROPHELP:
    DropHelp(mp1, mp2, hwnd, "fixme to give some help");
    return 0;

  case DM_DROP:
    ok = FALSE;
    if (emphasized) {
      emphasized = FALSE;
      // DrawTargetEmphasis(hwnd, emphasized);
    }
    pDInfo = (PDRAGINFO) mp1;		/* Get DRAGINFO pointer */
    if (pDInfo) {
      if (!DrgAccessDraginfo(pDInfo)) {
	Win_Error(HWND_DESKTOP, HWND_DESKTOP, pszSrcFile, __LINE__,
		  "DrgAccessDraginfo");
      }
      else {
	pDItem = DrgQueryDragitemPtr(pDInfo, 0);
	if (!pDItem)
	  Win_Error(hwnd, hwnd, pszSrcFile, __LINE__, "DM_DROP");
	/* Check valid rendering mechanisms and data */
	ok = DrgVerifyRMF(pDItem, DRM_LBOX, NULL)
			  && ~pDItem->fsControl & DC_PREPARE;
	if (ok) {
	  // ret = FullDrgName(pDItem,buffer,buflen);
	  /* note: targetfail is returned to source for all items */
	  DrgSendTransferMsg(pDInfo->hwndSource, DM_ENDCONVERSATION,
			     MPFROMLONG(pDItem->ulItemID),
			     MPFROMLONG(DMFL_TARGETSUCCESSFUL));
	}
	FreeDragInfoData(hwnd, pDInfo);
      }
    }
    return 0;
  } // switch
  return pfnOldProc ? pfnOldProc(hwnd, msg, mp1, mp2) :
    WinDefWindowProc(hwnd, msg, mp1, mp2);
}

//=== SBoxDlgProc() Select archiver to use or edit, supports list reorder too ===

static PSZ pszCantFindMsg = "Can't find item %d";

MRESULT EXPENTRY SBoxDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  ARC_TYPE **ppatReturn;	// Where to return selected archiver
  ARC_TYPE *pat;
  SHORT sSelect;
  SHORT sItemCount;
  CHAR szItemText[256];
  CHAR szPCItemText[256];	// Parent or child item text
  SHORT i;
  BOOL fShowAll;

  static SHORT sLastSelect = LIT_NONE;

  switch (msg) {
  case WM_INITDLG:
    if (!arcsigsloaded)
      load_archivers();
    if (!(ARC_TYPE **) mp2) {
      Runtime_Error2(pszSrcFile, __LINE__, IDS_NODATATEXT);
      WinDismissDlg(hwnd, 0);
      break;
    }
    /* Passed arg points to where to return selected archiver definition
     * On input arg value controls selection list content
     * If non-NULL, dup names are suppressed
     * If NULL, all definitions are shown
     */
    ppatReturn = (ARC_TYPE **) mp2;
    fShowAll = *ppatReturn == NULL;
    if (*ppatReturn)
      *ppatReturn = arcsighead;		// Preset to first
    WinSetWindowPtr(hwnd, QWL_USER, (PVOID) ppatReturn);
    fill_listbox(hwnd, fShowAll, sLastSelect);

#ifdef TEST_DRAG			// fixme
    {
      HWND hwnd2 = WinWindowFromID(hwnd, ASEL_LISTBOX);
      PFNWP pfn = WinSubclassWindow(hwnd2,
				    SDlgListboxSubclassProc);

      WinSetWindowPtr(hwnd2, QWL_USER, (PVOID) pfn);
    }
#endif // TEST_DRAG fixme

    break;

  case WM_COMMAND:
    ppatReturn = (ARC_TYPE **) WinQueryWindowPtr(hwnd, QWL_USER);
    switch (SHORT1FROMMP(mp1)) {
    case DID_OK:
      sSelect = (SHORT) WinSendDlgItemMsg(hwnd,
					  ASEL_LISTBOX,
					  LM_QUERYSELECTION,
					  MPFROMSHORT(LIT_FIRST), MPVOID);
      if (sSelect == LIT_NONE) {
	Runtime_Error(pszSrcFile, __LINE__, "list empty");
	return 0;
      }
      pat = arcsighead;
      if (*ppatReturn) {
	// If dups hidden, find archiver with matching id
	*szItemText = 0;
	WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMTEXT,
			  MPFROM2SHORT(sSelect, 255), MPFROMP(szItemText));
	if (!*szItemText)
	  pat = NULL;
	else {
	  for (; pat; pat = pat->next) {
	    if (pat->id && !strcmp(szItemText, pat->id))
	      break;			// Found it
	  }
	}
      }
      else {
	// If dups not hidden, lookup by count
	for (i = 0; pat && i < sSelect; i++, pat = pat->next) ;	// Scan
      }
      if (pat && (!*ppatReturn || (pat->id && pat->extract && pat->create))) {
	*ppatReturn = pat;
      }
      else {
	Runtime_Error(pszSrcFile, __LINE__, "no match");
	// Refuse to select
	WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			  MPFROMSHORT(LIT_NONE), FALSE);
	return 0;
      }
      sLastSelect = sSelect;
      WinDismissDlg(hwnd, TRUE);
      return 0;

    case DID_CANCEL:
      if (arcsigsmodified) {
	if (saymsg(MB_YESNO,
		   hwnd,
		   GetPString(IDS_ADCHANGESINMEMTEXT),
		   GetPString(IDS_ADREWRITETEXT), NullStr) == MBID_YES) {
	  PSZ ab2 = searchpath(GetPString(IDS_ARCHIVERBB2));	// Rewrite without prompting

	  rewrite_archiverbb2(ab2);
	}
      }
      sSelect = (SHORT) WinSendDlgItemMsg(hwnd,
					  ASEL_LISTBOX,
					  LM_QUERYSELECTION,
					  MPFROMSHORT(LIT_FIRST), MPVOID);
      if (sSelect != LIT_NONE)
	sLastSelect = sSelect;
      *ppatReturn = NULL;
      PostMsg(hwnd, WM_CLOSE, MPVOID, MPVOID);	// fixme to understand why needed
      return 0;

    case ASEL_PB_ADD:
      sSelect = (SHORT) WinSendDlgItemMsg(hwnd,
					  ASEL_LISTBOX,
					  LM_QUERYSELECTION,
					  MPFROMSHORT(LIT_FIRST), MPVOID);
      if (sSelect != LIT_NONE) {
	ARCDUMP ad;

	memset(&ad, 0, sizeof(ARCDUMP));
	ad.info = xmallocz(sizeof(ARC_TYPE), pszSrcFile, __LINE__);
	if (ad.info) {
	  if (!WinDlgBox(HWND_DESKTOP,
			 hwnd,
			 ArcReviewDlgProc,
			 FM3ModHandle, AD_FRAME, MPFROMP(&ad))) {
	    free(ad.info);
	  }
	  else {
	    // Find self - assume all archivers listed since we are editing
	    for (i = 0, pat = arcsighead; pat && i < sSelect; pat = pat->next, i++) ;	// Find self

	    if (!pat) {
	      if (arcsighead)
		Runtime_Error(pszSrcFile, __LINE__, pszCantFindMsg, sSelect);
	      else
		arcsighead = ad.info;
	    }
	    else {
	      // Insert before
	      if (pat->prev) {
		ad.info->next = pat;
		ad.info->prev = pat->prev;
		pat->prev->next = ad.info;
		pat->prev = ad.info;
	      }
	      else {
		arcsighead = ad.info;
		ad.info->next = pat;
		pat->prev = ad.info;
	      }
	    }
	    WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_INSERTITEM,
			      MPFROM2SHORT(sSelect, 0),
			      MPFROMP(ad.info->id ? ad.info->id : "?"));
	    WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			      MPFROMSHORT(sSelect - 1), MPFROMSHORT(TRUE));
	    arcsigsmodified = TRUE;
	  }
	}
      }
      return 0;
    case ASEL_PB_DELETE:
      sSelect = (SHORT) WinSendDlgItemMsg(hwnd,
					  ASEL_LISTBOX,
					  LM_QUERYSELECTION,
					  MPFROMSHORT(LIT_FIRST), MPVOID);
      if (sSelect != LIT_NONE) {
	// Find self - assume all archivers listed since we are editing
	for (i = 0, pat = arcsighead; pat && i < sSelect; pat = pat->next, i++) ;	// Find self

	if (!pat)
	  Runtime_Error(pszSrcFile, __LINE__, pszCantFindMsg, sSelect);
	else {
	  // Delete current
	  if (pat->prev) {
	    pat->prev->next = pat->next;
	    if (pat->next)
	      pat->next->prev = pat->prev;
	  }
	  else {
	    arcsighead = pat->next;
	    if (pat->next)
	      pat->next->prev = pat->prev;
	  }
	}
	free_arc_type(pat);
	arcsigsmodified = TRUE;
	WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_DELETEITEM,
			  MPFROM2SHORT(sSelect, 0), MPVOID);
	sItemCount =
	  (SHORT) WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMCOUNT,
				    MPVOID, MPVOID);
	if (sSelect >= sItemCount)
	  sSelect--;
	if (sSelect >= 0) {
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			    MPFROMSHORT(sSelect), MPFROMSHORT(TRUE));
	}
      }
      return 0;
    case ASEL_PB_UP:
      sSelect = (SHORT) WinSendDlgItemMsg(hwnd,
					  ASEL_LISTBOX,
					  LM_QUERYSELECTION,
					  MPFROMSHORT(LIT_FIRST), MPVOID);
      if (sSelect != LIT_NONE && sSelect > 0) {
	// Find self - assume all archivers listed since we are editing
	for (i = 0, pat = arcsighead; pat && i < sSelect; pat = pat->next, i++) ;	// Find self
	if (!pat || !pat->prev)
	  Runtime_Error(pszSrcFile, __LINE__, pszCantFindMsg, sSelect);
	else {
	  ARC_TYPE *patGDad;
	  ARC_TYPE *patDad;
	  ARC_TYPE *patChild;

	  patChild = pat->next;
	  patDad = pat->prev;
	  patGDad = patDad->prev;
	  patDad->next = patChild;
	  if (patChild)
	    patChild->prev = patDad;
	  patDad->prev = pat;
	  pat->next = patDad;
	  if (patGDad) {
	    patGDad->next = pat;
	    pat->prev = patGDad;
	  }
	  else {
	    arcsighead = pat;
	    pat->prev = NULL;
	  }

	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMTEXT,
			    MPFROM2SHORT(sSelect, 255), MPFROMP(szItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMTEXT,
			    MPFROM2SHORT(sSelect - 1, 255),
			    MPFROMP(szPCItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SETITEMTEXT,
			    MPFROMSHORT(sSelect), MPFROMP(szPCItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SETITEMTEXT,
			    MPFROMSHORT(sSelect - 1), MPFROMP(szItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			    MPFROMSHORT(sSelect - 1), MPFROMSHORT(TRUE));
	  arcsigsmodified = TRUE;
	}
      }
      return 0;
    case ASEL_PB_DOWN:
      sSelect =
	(SHORT) WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYSELECTION,
				  MPFROMSHORT(LIT_FIRST), MPVOID);
      sItemCount =
	(SHORT) WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMCOUNT,
				  MPVOID, MPVOID);
      if (sSelect != LIT_NONE && sSelect < sItemCount - 1) {
	// Find self - assume all archivers listed since we are editing
	for (i = 0, pat = arcsighead; pat && i < sSelect; pat = pat->next, i++) ;	// Find self
	if (!pat || !pat->next)
	  Runtime_Error(pszSrcFile, __LINE__, "Can't find item %d of %d",
			sSelect, sItemCount);
	else {
	  ARC_TYPE *patDad;
	  ARC_TYPE *patChild;

	  patDad = pat->prev;
	  patChild = pat->next;
	  pat->next = patChild->next;
	  patChild->next = pat;
	  pat->prev = patChild;
	  patChild->prev = patDad;
	  if (patDad) {
	    patDad->next = patChild;
	    patChild->prev = patDad;
	  }
	  else {
	    arcsighead = patChild;
	    patChild->prev = NULL;
	  }

	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMTEXT,
			    MPFROM2SHORT(sSelect, 255), MPFROMP(szItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYITEMTEXT,
			    MPFROM2SHORT(sSelect + 1, 255),
			    MPFROMP(szPCItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SETITEMTEXT,
			    MPFROMSHORT(sSelect), MPFROMP(szPCItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SETITEMTEXT,
			    MPFROMSHORT(sSelect + 1), MPFROMP(szItemText));
	  WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_SELECTITEM,
			    MPFROMSHORT(sSelect + 1), MPFROMSHORT(TRUE));
	  arcsigsmodified = TRUE;
	}
      }
      return 0;

    case ASEL_PB_REVERT:
      // Reload without checking in case changed outside
      sSelect =
	(SHORT) WinSendDlgItemMsg(hwnd, ASEL_LISTBOX, LM_QUERYSELECTION,
				  MPFROMSHORT(LIT_FIRST), MPVOID);
      load_archivers();
      fill_listbox(hwnd, TRUE, sSelect);
      return 0;

    case IDM_HELP:
      if (hwndHelp) {
	WinSendMsg(hwndHelp, HM_DISPLAY_HELP, MPFROM2SHORT(HELP_EDITARC, 0),	// fixme to be HELP_SELARC
		   MPFROMSHORT(HM_RESOURCEID));
      }
    }
    return 0;				// WM_COMMAND

  case WM_CONTROL:
    if (SHORT1FROMMP(mp1) == ASEL_LISTBOX && SHORT2FROMMP(mp1) == LN_ENTER)
      PostMsg(hwnd, WM_COMMAND, MPFROM2SHORT(DID_OK, 0), MPVOID);
    return 0;

  case WM_CLOSE:
    WinDismissDlg(hwnd, FALSE);
    return 0;

  default:
    break;
  }
  return WinDefDlgProc(hwnd, msg, mp1, mp2);
}

/*
   see archiver.tmp
   02-08-96  23:55              1
   8 Feb 96 23:55:32            2
   8 Feb 96  11:55p             3
   96-02-08 23:55:32            4
   31-02-98  23:55              5
 */

BOOL ArcDateTime(CHAR * dt, INT type, CDATE * cdate, CTIME * ctime)
{
  INT x;
  BOOL ret = FALSE;
  CHAR *p, *pp, *pd;

  if (dt && cdate && ctime) {
    memset(cdate, 0, sizeof(CDATE));
    memset(ctime, 0, sizeof(CTIME));
    if (type) {
      p = dt;
      while (*p && *p == ' ')
	p++;
      pd = dt;
      switch (type) {
      case 1:
	cdate->month = atoi(pd);
	p = to_delim(pd, "-/.");
	if (p) {
	  p++;
	  cdate->day = atoi(p);
	  pd = p;
	  p = to_delim(pd, "-/.");
	  if (p) {
	    p++;
	    cdate->year = atoi(p);
	    if (cdate->year > 80 && cdate->year < 1900)
	      cdate->year += 1900;
	    else if (cdate->year < 1900)
	      cdate->year += 2000;
	    ret = TRUE;
	    p = strchr(p, ' ');
	    if (p) {
	      while (*p && *p == ' ')
		p++;
	      ctime->hours = atoi(p);
	      p = to_delim(pd, ":.");
	      if (p) {
		p++;
		ctime->minutes = atoi(p);
		p = to_delim(pd, ":.");
		if (p) {
		  p++;
		  ctime->seconds = atoi(p);
		}
	      }
	    }
	  }
	}
	break;

      case 2:
	cdate->day = atoi(p);
	p = strchr(p, ' ');
	if (p) {
	  p++;
	  for (x = 0; x < 12; x++) {
	    if (!strnicmp(p, GetPString(IDS_JANUARY + x), 3))
	      break;
	  }
	  if (x < 12) {
	    cdate->month = x;
	    p = strchr(p, ' ');
	    if (p) {
	      p++;
	      cdate->year = atoi(p);
	      if (cdate->year > 80 && cdate->year < 1900)
		cdate->year += 1900;
	      else if (cdate->year < 1900)
		cdate->year += 2000;
	      ret = TRUE;
	      p = strchr(p, ' ');
	      if (p) {
		while (*p && *p == ' ')
		  p++;
		ctime->hours = atoi(p);
		p = to_delim(pd, ":.");
		if (p) {
		  p++;
		  ctime->minutes = atoi(p);
		  p = to_delim(pd, ":.");
		  if (p) {
		    p++;
		    ctime->seconds = atoi(p);
		  }
		}
	      }
	    }
	  }
	}
	break;

      case 3:
	cdate->day = atoi(p);
	p = strchr(p, ' ');
	if (p) {
	  p++;
	  for (x = 0; x < 12; x++) {
	    if (!strnicmp(p, GetPString(IDS_JANUARY + x), 3))
	      break;
	  }
	  if (x < 12) {
	    cdate->month = x;
	    p = strchr(p, ' ');
	    if (p) {
	      p++;
	      cdate->year = atoi(p);
	      if (cdate->year > 80 && cdate->year < 1900)
		cdate->year += 1900;
	      else if (cdate->year < 1900)
		cdate->year += 2000;
	      ret = TRUE;
	      p = strchr(p, ' ');
	      if (p) {
		while (*p && *p == ' ')
		  p++;
		ctime->hours = atoi(p);
		p = to_delim(pd, ":.");
		if (p) {
		  p++;
		  pp = p;
		  ctime->minutes = atoi(p);
		  p = to_delim(pd, ":.");
		  if (p) {
		    p++;
		    ctime->seconds = atoi(p);
		    p += 2;
		    if (toupper(*p) == 'P')
		      ctime->hours += 12;
		  }
		  else {
		    p = pp;
		    p += 2;
		    if (toupper(*p) == 'P')
		      ctime->hours += 12;
		  }
		}
	      }
	    }
	  }
	}
	break;

      case 4:
	cdate->year = atoi(p);
	if (cdate->year > 80 && cdate->year < 1900)
	  cdate->year += 1900;
	else if (cdate->year < 1900)
	  cdate->year += 2000;
	p = to_delim(pd, "-/.");
	if (p) {
	  p++;
	  cdate->month = atoi(p);
	  pd = p;
	  p = to_delim(pd, "-/.");
	  if (p) {
	    p++;
	    cdate->day = atoi(p);
	    ret = TRUE;
	    p = strchr(p, ' ');
	    if (p) {
	      while (*p && *p == ' ')
		p++;
	      ctime->hours = atoi(p);
	      p = to_delim(pd, ":.");
	      if (p) {
		p++;
		ctime->minutes = atoi(p);
		p = to_delim(pd, ":.");
		if (p) {
		  p++;
		  ctime->seconds = atoi(p);
		}
	      }
	    }
	  }
	}
	break;

      case 5:
	cdate->day = atoi(pd);
	p = to_delim(pd, "-/.");
	if (p) {
	  p++;
	  cdate->month = atoi(p);
	  pd = p;
	  p = to_delim(pd, "-/.");
	  if (p) {
	    p++;
	    cdate->year = atoi(p);
	    if (cdate->year > 80 && cdate->year < 1900)
	      cdate->year += 1900;
	    else if (cdate->year < 1900)
	      cdate->year += 2000;
	    ret = TRUE;
	    p = strchr(p, ' ');
	    if (p) {
	      while (*p && *p == ' ')
		p++;
	      ctime->hours = atoi(p);
	      p = to_delim(pd, ":.");
	      if (p) {
		p++;
		ctime->minutes = atoi(p);
		p = to_delim(pd, ":.");
		if (p) {
		  p++;
		  ctime->seconds = atoi(p);
		}
	      }
	    }
	  }
	}
	break;

      default:
	break;
      }
    }
  }
  return ret;
}

#pragma alloc_text(MISC9,quick_find_type,find_type)
#pragma alloc_text(AVL,load_archivers, get_line_strip_comments, get_line_strip_white)
#pragma alloc_text(FMARCHIVE,SBoxDlgProc,SDlgListboxSubclassProc)
#pragma alloc_text(ARCCNRS,ArcDateTime)
