/*   dlgutil2.c
* ===========================================================================
*
*                            PUBLIC DOMAIN NOTICE
*            National Center for Biotechnology Information (NCBI)
*
*  This software/database is a "United States Government Work" under the
*  terms of the United States Copyright Act.  It was written as part of
*  the author's official duties as a United States Government employee and
*  thus cannot be copyrighted.  This software/database is freely available
*  to the public for use. The National Library of Medicine and the U.S.
*  Government do not place any restriction on its use or reproduction.
*  We would, however, appreciate having the NCBI and the author cited in
*  any work or product based on this material
*
*  Although all reasonable efforts have been taken to ensure the accuracy
*  and reliability of the software and data, the NLM and the U.S.
*  Government do not and cannot warrant the performance or results that
*  may be obtained by using this software or data. The NLM and the U.S.
*  Government disclaim all warranties, express or implied, including
*  warranties of performance, merchantability or fitness for any particular
*  purpose.
*
* ===========================================================================
*
* File Name:  dlgutil2.c
*
* Author:  Jonathan Kans
*
* Version Creation Date:   1/22/95
*
* $Revision: 6.246 $
*
* File Description: 
*
* Modifications:  
* --------------------------------------------------------------------------
* Date     Name        Description of modification
* -------  ----------  -----------------------------------------------------
*
*
* ==========================================================================
*/

#include <dlogutil.h>
#include <document.h>
#include <gather.h>
#include <subutil.h>
#include <objfdef.h>
#include <gbfeat.h>
#include <gbftdef.h>
#include <utilpub.h>
#include <objfeat.h>
#include <objseq.h>
#include <toasn3.h>
#include <explore.h>
#include <findrepl.h>
#include <valid.h>
#include <salparam.h>
#ifdef WIN_MOTIF
#include <netscape.h>
#endif
#define NLM_GENERATED_CODE_PROTO
#include <objmacro.h>
#include <macroapi.h>

typedef struct datepage {
  DIALOG_MESSAGE_BLOCK
  TexT          year;
  PopuP         year_popup;
  Int4          start_year;
  Int4          num_years;
  PopuP         month;
  TexT          day;
} DatePage, PNTR DatePagePtr;

extern CharPtr SaveStringFromTextAndStripNewlines (TexT t)

{
  Char     ch;
  size_t   len;
  CharPtr  ptr;
  CharPtr  str;

  len = TextLength (t);
  if (len > 0) {
    str = MemNew (len + 1);
    if (str != NULL) {
      GetTitle (t, str, len + 1);
      ptr = str;
      ch = *ptr;
      while (ch != '\0') {
        if (ch < ' ') {
          *ptr = ' ';
        }
        ptr++;
        ch = *ptr;
      }
      TrimSpacesAroundString (str);
      if (StringHasNoText (str)) {
        str = MemFree (str);
      }
      return str;
    } else {
      return NULL;
    }
  } else {
    return NULL;
  }
}


extern CharPtr StripNewlines (CharPtr str)

{
  Char     ch;
  size_t   len;
  CharPtr  ptr;

  if (str == NULL) return str;
  len = StringLen (str);
  if (len > 0) {
    ptr = str;
    ch = *ptr;
    while (ch != '\0') {
      if (ch < ' ') {
        *ptr = ' ';
      }
      ptr++;
      ch = *ptr;
    }
    TrimSpacesAroundString (str);
    if (StringHasNoText (str)) {
      str = MemFree (str);
    }
  } else {
    str = MemFree (str);
  }
  return str;
}


extern void NewlinesToTildes (CharPtr str)

{
  Uchar    ch;
  CharPtr  ptr;

  if (StringHasNoText (str)) return;
  ptr = str;
  ch = *ptr;
  while (ch != '\0') {
    if (ch < ' ') {
      *ptr = '~';
    }
    ptr++;
    ch = *ptr;
  }
}


static void DatePtrToDatePage (DialoG d, Pointer data)

{
  DatePtr      dp;
  DatePagePtr  dpp;
  Int2         day;
  Char         str [32];
  Int2         year, val;


  dpp = (DatePagePtr) GetObjectExtra (d);
  dp = (DatePtr) data;
  if (dpp != NULL) {
    if (dp == NULL || dp->data[0] != 1) {
      SafeSetValue (dpp->month, 1);
      SafeSetTitle (dpp->day, "");
      SafeSetTitle (dpp->year, "");
      SafeSetValue (dpp->year_popup, 1);
    } else {
      /* set month */
      SetEnumPopup (dpp->month, months_alist, (UIEnum) dp->data [2]);
      /* set day */
      day = (Int2) dp->data [3];
      if (day > 0 && day <= 31) {
        sprintf (str, "%d", (int) day);
        SafeSetTitle (dpp->day, str);
      } else {
        SafeSetTitle (dpp->day, "");
      }
      /* set year */
      year = (Int2) dp->data [1];
      if (year > 0) {
        if (dpp->year_popup == NULL) {
          sprintf (str, "%d", (int) (year + 1900));
          SafeSetTitle (dpp->year, str);
        } else {
          val = year + 1900 - dpp->start_year + 1;
          if (val < 1 || val >= dpp->num_years) {
            sprintf (str, "%d", (int) (year + 1900));
            SafeSetTitle (dpp->year, str);
            dpp->year_popup = NULL;
            Show (dpp->year);
          } else {
            SetValue (dpp->year_popup, val);
          }
        }
      } else {
        if (dpp->year_popup == NULL) {
          SafeSetTitle (dpp->year, "");
        } else {
          SetValue (dpp->year_popup, 1);
        }
      }
    }
  }
}

static Pointer DatePageToDatePtr (DialoG d)

{
  DatePtr      dp;
  DatePagePtr  dpp;
  Int2         day;
  UIEnum       month;
  Char         str [32];
  Int2         year = 0;

  dp = NULL;
  dpp = (DatePagePtr) GetObjectExtra (d);
  if (dpp != NULL) {
    dp = DateNew ();
    if (dp != NULL) {
      dp->data [0] = 1;
      /* get year value */
      if (dpp->year_popup != NULL) {
        year = GetValue (dpp->year_popup);
        if (year > 0) {
          year += dpp->start_year - 1;
        } else {
          dp = DateFree (dp);
          return NULL;
        }
      } else {
        GetTitle (dpp->year, str, sizeof (str));
        if (! StringHasNoText (str)) {
          StrToInt (str, &year);
        }
      }
      if (year >= 1900) {
        dp->data [1] = (Uint1) (year - 1900);
      } else {
        dp = DateFree (dp);
        return dp;
      }
      /* get month value */
      if (GetEnumPopup (dpp->month, months_alist, &month)) {
        dp->data [2] = (Uint1) month;
      } else {
        dp = DateFree (dp);
        return dp;
      }
      /* get day value */
      GetTitle (dpp->day, str, sizeof (str));
      StrToInt (str, &day);
      dp->data [3] = (Uint1) day;
    }
  }
  return (Pointer) dp;
}

extern DialoG CreateDateDialogEx (GrouP prnt, CharPtr title, Int4 start_year, Int4 num_years)

{
  DatePagePtr  dpp;
  GrouP        f;
  GrouP        m;
  GrouP        p;
  GrouP        s;
  GrouP        year_grp;
  Char         year_buf[15];
  Int4         i;

  p = HiddenGroup (prnt, 1, 0, NULL);
  SetGroupSpacing (p, 10, 10);

  dpp = (DatePagePtr) MemNew (sizeof (DatePage));
  if (dpp) {

    SetObjectExtra (p, dpp, StdCleanupExtraProc);
    dpp->dialog = (DialoG) p;
    dpp->todialog = DatePtrToDatePage;
    dpp->fromdialog = DatePageToDatePtr;
    dpp->testdialog = NULL;

    dpp->start_year = start_year;
    dpp->num_years = num_years;

    if (title != NULL && title [0] != '\0') {
      s = NormalGroup (p, 0, -2, title, systemFont, NULL);
    } else {
      s = HiddenGroup (p, 0, -2, NULL);
    }
    m = HiddenGroup (s, -1, 0, NULL);
    /*
    SetGroupSpacing (m, 10, 10);
    */

    f = HiddenGroup (m, -6, 0, NULL);
    StaticPrompt (f, "Month", 0, popupMenuHeight, programFont, 'l');
    dpp->month = PopupList (f, TRUE, NULL);
    InitEnumPopup (dpp->month, months_alist, NULL);
    SetValue (dpp->month, 1);
    StaticPrompt (f, "Day", 0, dialogTextHeight, programFont, 'l');
    dpp->day = DialogText (f, "", 4, NULL);
    StaticPrompt (f, "Year", 0, dialogTextHeight, programFont, 'l');
    year_grp = HiddenGroup (f, 0, 0, NULL);
    if (start_year > 0 && num_years > -1) {
      dpp->year_popup = PopupList (year_grp, TRUE, NULL);
      for (i = 0; i < num_years; i++) {
        sprintf (year_buf, "%d", start_year + i);
        PopupItem (dpp->year_popup, year_buf);
      }
    }
    dpp->year = DialogText (year_grp, "", 6, NULL);
    if (dpp->year_popup != NULL) {
      SafeHide (dpp->year);
    }
    AlignObjects (ALIGN_CENTER, (HANDLE)dpp->year, (HANDLE)dpp->year_popup, NULL);
  }

  return (DialoG) p;
}


extern DialoG CreateDateDialog (GrouP prnt, CharPtr title)

{
  return CreateDateDialogEx (prnt, title, -1, 0);
}


typedef struct featcit {
  DIALOG_MESSAGE_BLOCK
  DoC         citdoc;
  ValNodePtr  pubset;
} FeatCitPage, PNTR FeatCitPagePtr;

static ParData cofParFmt = {FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0};
static ColData cofColFmt = {0, 0, 0, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, TRUE};

static ParData cofeParFmt = {TRUE, FALSE, FALSE, FALSE, FALSE, 0, 0};
static ColData cofeColFmt = {0, 0, 0, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, TRUE};

static Uint1 diamondSym [] = {
  0x00, 0x10, 0x38, 0x7C, 0x38, 0x10, 0x00, 0x00
};

static void DrawCitOnFeat (DoC d, RectPtr r, Int2 item, Int2 firstLine)

{
  Int2  lineHeight;
  RecT  rct;

  if (d != NULL && r != NULL && item > 0 && firstLine == 0) {
    if (item == 1) return; /* citonfeattxt or citonfeathdr explanatory text */
    GetItemParams (d, item, NULL, NULL, NULL, &lineHeight, NULL);
    rct = *r;
    rct.left += 1;
    rct.right = rct.left + 7;
    rct.top += (lineHeight - 7) / 2;
    rct.bottom = rct.top + 7;
    CopyBits (&rct, diamondSym);
    /*
    x = r->left + 1;
    y = r->top + lineHeight / 2;
    MoveTo (x, y);
    LineTo (x + 5, y);
    */
  }
}

static int LIBCALLBACK CompareStrings (VoidPtr ptr1, VoidPtr ptr2)

{
  CharPtr  str1;
  CharPtr  str2;

  if (ptr1 != NULL && ptr2 != NULL) {
    str1 = *((CharPtr PNTR) ptr1);
    str2 = *((CharPtr PNTR) ptr2);
    if (str1 != NULL && str2 != NULL) {
      return StringICmp (str1, str2);
    } else {
      return 0;
    }
  } else {
    return 0;
  }
}

static CharPtr citonfeattxt =
"Press 'Edit Citations' to attach publications to this feature. \
Publications must first be added to the record. For biological \
justification, and not to credit the sequencer, create publication \
with the 'Cites a feature on the sequence' scope.\n";

static CharPtr citonfeathdr =
"Press 'Edit Citations' to change publications.\n\n";

static void PubsetPtrToFeatCitPage (DialoG d, Pointer data)

{
  Int2            count;
  FeatCitPagePtr  fpp;
  Int2            i;
  Char            label [128];
  ObjMgrPtr       omp;
  ObjMgrTypePtr   omtp;
  ValNodePtr      ppr;
  ValNodePtr      psp;
  RecT            r;
  CharPtr         PNTR strs;

  fpp = (FeatCitPagePtr) GetObjectExtra (d);
  psp = (ValNodePtr) data;
  if (fpp != NULL) {
    Reset (fpp->citdoc);
    fpp->pubset = PubSetFree (fpp->pubset);
    fpp->pubset = AsnIoMemCopy (data,
                                (AsnReadFunc) PubSetAsnRead,
                                (AsnWriteFunc) PubSetAsnWrite);
    SetDocAutoAdjust (fpp->citdoc, FALSE);
    ObjectRect (fpp->citdoc, &r);
    InsetRect (&r, 4, 4);
    cofColFmt.pixWidth = r.right - r.left;
    cofColFmt.pixInset = 10;
    omp = ObjMgrGet ();
    if (omp == NULL) return;
    omtp = ObjMgrTypeFind (omp, OBJ_SEQFEAT_CIT, NULL, NULL);
    if (omtp == NULL || omtp->labelfunc == NULL) return;
    if (psp != NULL && psp->data.ptrvalue != NULL) {
      count = 0;
      for (ppr = psp->data.ptrvalue; ppr != NULL; ppr = ppr->next) {
        count++;
      }
      if (count > 0) {
        strs = MemNew (sizeof (CharPtr) * (size_t) (count + 1));
        if (strs != NULL) {
          i = 0;
          for (ppr = psp->data.ptrvalue; ppr != NULL; ppr = ppr->next) {
            (*(omtp->labelfunc)) (ppr, label, 127, OM_LABEL_CONTENT);
            strs [i] = StringSave (label);
            i++;
          }
          HeapSort (strs, count, sizeof (CharPtr), CompareStrings);
          AppendText (fpp->citdoc, citonfeathdr, &cofParFmt, NULL, programFont);
          for (i = 0; i < count; i++) {
            AppendText (fpp->citdoc, strs [i],
                        &cofParFmt, &cofColFmt, programFont);
          }
          for (i = 0; i < count; i++) {
            strs [i] = MemFree (strs [i]);
          }
          MemFree (strs);
        } else {
          AppendText (fpp->citdoc, citonfeattxt, &cofParFmt, NULL, programFont);
        }
      }
    } else {
      AppendText (fpp->citdoc, citonfeattxt, &cofParFmt, NULL, programFont);
    }
    SetDocShade (fpp->citdoc, DrawCitOnFeat, NULL, NULL, NULL);
    UpdateDocument (fpp->citdoc, 0, 0);
  }
}

static Pointer FeatCitPageToPubsetPtr (DialoG d)

{
  FeatCitPagePtr  fpp;
  ValNodePtr      psp;

  psp = NULL;
  fpp = (FeatCitPagePtr) GetObjectExtra (d);
  if (fpp != NULL) {
    psp = AsnIoMemCopy (fpp->pubset,
                        (AsnReadFunc) PubSetAsnRead,
                        (AsnWriteFunc) PubSetAsnWrite);
  }
  return (Pointer) psp;
}

static void CleanupCitOnFeatProc (GraphiC g, VoidPtr data)

{
  FeatCitPagePtr  fpp;

  fpp = (FeatCitPagePtr) data;
  if (fpp != NULL) {
    PubSetFree (fpp->pubset);
  }
  MemFree (data);
}

static DialoG CreateCitOnFeatDialog (GrouP h, CharPtr title)

{
  FeatCitPagePtr  fpp;
  Int2            lineHeight;
  GrouP           m;
  GrouP           p;
  GrouP           s;

  p = HiddenGroup (h, 1, 0, NULL);
  SetGroupSpacing (p, 10, 10);

  fpp = (FeatCitPagePtr) MemNew (sizeof (FeatCitPage));
  if (fpp != NULL) {

    SetObjectExtra (p, fpp, CleanupCitOnFeatProc);
    fpp->dialog = (DialoG) p;
    fpp->todialog = PubsetPtrToFeatCitPage;
    fpp->fromdialog = FeatCitPageToPubsetPtr;
    fpp->testdialog = NULL;

    if (title != NULL && title [0] != '\0') {
      s = NormalGroup (p, 0, -2, title, systemFont, NULL);
    } else {
      s = HiddenGroup (p, 0, -2, NULL);
    }
    m = HiddenGroup (s, -1, 0, NULL);
    /*
    SetGroupSpacing (m, 10, 10);
    */

    SelectFont (programFont);
    lineHeight = LineHeight ();
    SelectFont (systemFont);
    cofParFmt.minHeight = lineHeight + 2;
    StaticPrompt (m, "Citations on Feature", 25 * stdCharWidth, 0, programFont, 'c');
    fpp->citdoc = DocumentPanel (m, 25 * stdCharWidth, 5 * cofParFmt.minHeight);
    SetObjectExtra (fpp->citdoc, fpp, NULL);
    SetDocAutoAdjust (fpp->citdoc, FALSE);
    fpp->pubset = NULL;
  }

  return (DialoG) p;
}

typedef struct citlist {
  Uint2    entityID;
  Uint4    itemID;
  Uint2    itemtype;
  CharPtr  label;
} CitListData, PNTR CitListDataPtr;

typedef struct featcitedit {
  DIALOG_MESSAGE_BLOCK
  DoC             allcitdoc;
  Int2            clickedItem;
  Int2            clickedRow;
  Int2            numitems;
  Int2            lineheight;
  Int2            index;
  BoolPtr         chosen;
  ValNodePtr      citlist;
  ValNodePtr      psp;
  ObjMgrPtr       omp;
  Int2            entityID;
} FeatCitEdit, PNTR FeatCitEditPtr;

static void CleanupFeatCitForm (GraphiC g, VoidPtr data)

{
  FeatCitEditPtr  fcep;

  fcep = (FeatCitEditPtr) data;
  if (fcep != NULL) {
    MemFree (fcep->chosen);
  }
  MemFree (data);
}

static void DrawFeatCit (DoC d, RectPtr r, Int2 item, Int2 firstLine)

{
  FeatCitEditPtr  fcep;
  RecT            rct;

  fcep = (FeatCitEditPtr) GetObjectExtra (d);
  if (fcep != NULL && r != NULL && item > 0 && firstLine == 0) {
    rct = *r;
    rct.left += 1;
    rct.right = rct.left + fcep->lineheight;
    rct.bottom = rct.top + (rct.right - rct.left);
    FrameRect (&rct);
    if (item > 0 && item <= fcep->numitems) {
      if (fcep->chosen != NULL && fcep->chosen [item - 1]) {
        MoveTo (rct.left, rct.top);
        LineTo (rct.right - 1, rct.bottom - 1);
        MoveTo (rct.left, rct.bottom - 1);
        LineTo (rct.right - 1, rct.top);
      }
    }
  }
}

static void ClickFeatCit (DoC d, PoinT pt)

{
}

static void ReleaseFeatCit (DoC d, PoinT pt)

{
  Int2            col;
  FeatCitEditPtr  fcep;
  Int2            item;
  RecT            rct;
  Int2            row;

  fcep = (FeatCitEditPtr) GetObjectExtra (d);
  if (fcep != NULL && fcep->chosen != NULL) {
    MapDocPoint (d, pt, &item, &row, &col, &rct);
    rct.left += 1;
    rct.right = rct.left + fcep->lineheight;
    rct.bottom = rct.top + (rct.right - rct.left);
    if (row == 1 && col == 1 && item > 0 && item <= fcep->numitems && PtInRect (pt, &rct)) {
      if (fcep->chosen [item - 1]) {
        fcep->chosen [item - 1] = FALSE;
      } else {
        fcep->chosen [item - 1] = TRUE;
      }
      InsetRect (&rct, -1, -1);
      InvalRect (&rct);
      Update ();
    }
  }
}

static Boolean GatherAllCits (GatherContextPtr gcp)

{
  CitListDataPtr  cldp;
  FeatCitEditPtr  fcep;
  Char            label [128];
  ObjMgrTypePtr   omtp;
  PubdescPtr      pdp;
  ValNodePtr      sdp;
  SeqFeatPtr      sfp;
  ValNodePtr      vnp;

  if (gcp == NULL) return TRUE;
  fcep = (FeatCitEditPtr) gcp->userdata;
  if (fcep == NULL) return TRUE;
  label [0] = '\0';
  pdp = NULL;
  if (gcp->thistype == OBJ_SEQDESC) {
    sdp = (ValNodePtr) gcp->thisitem;
    if (sdp == NULL || sdp->choice != Seq_descr_pub) return TRUE;
    pdp = sdp->data.ptrvalue;
  } else if (gcp->thistype == OBJ_SEQFEAT) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp == NULL || sfp->data.choice != SEQFEAT_PUB) return TRUE;
    pdp = sfp->data.value.ptrvalue;
  } else return TRUE;
  if (pdp == NULL) return TRUE;
  omtp = ObjMgrTypeFind (fcep->omp, gcp->thistype, NULL, NULL);
  if (omtp == NULL || omtp->labelfunc == NULL) return TRUE;
  (*(omtp->labelfunc)) (gcp->thisitem, label, 127, OM_LABEL_CONTENT);
  cldp = (CitListDataPtr) MemNew (sizeof (CitListData));
  if (cldp != NULL) {
    vnp = ValNodeNew (fcep->citlist);
    if (fcep->citlist == NULL) {
      fcep->citlist = vnp;
    }
    if (vnp != NULL) {
      vnp->data.ptrvalue = cldp;
      cldp->entityID = gcp->entityID;
      cldp->itemID = gcp->itemID;
      cldp->itemtype = gcp->thistype;
      cldp->label = StringSave (label);
      (fcep->numitems)++;
    } else {
      MemFree (cldp);
      return TRUE;
    }
  }
  return TRUE;
}

static Boolean PrintCitOnFeatItem (GatherContextPtr gcp)

{
  CharPtr  PNTR  rsultp;
  ValNodePtr     sdp;
  SeqFeatPtr     sfp;
  Boolean        success;

  rsultp = (CharPtr PNTR) gcp->userdata;
  if (rsultp != NULL) {
    success = FALSE;
    switch (gcp->thistype) {
      case OBJ_SEQDESC :
        sdp = (ValNodePtr) gcp->thisitem;
        if (sdp->data.ptrvalue != NULL) {
          success = StdFormatPrint ((Pointer) sdp, (AsnWriteFunc) SeqDescAsnWrite,
                                    "StdSeqDesc", spop);
        } else {
          *rsultp = StringSave ("Empty Descriptor\n");
          success = TRUE;
        }
        break;
      case OBJ_SEQFEAT :
        sfp = (SeqFeatPtr) gcp->thisitem;
        if (sfp != NULL && (sfp->data.choice == 10 || sfp->data.value.ptrvalue != NULL)) {
          success = StdFormatPrint ((Pointer) sfp, (AsnWriteFunc) SeqFeatAsnWrite,
                                    "StdSeqFeat", spop);
        } else {
          *rsultp = StringSave ("Empty Feature\n");
          success = TRUE;
        }
        break;
      default :
        break;
    }
    if (success) {
      if (spop->ptr != NULL && *((CharPtr) (spop->ptr)) != '\0') {
        *rsultp = spop->ptr;
        spop->ptr = NULL;
      } else {
        *rsultp = StringSave ("Empty Data\n");
      }
    } else {
      *rsultp = StringSave ("Data Failure\n");
    }
  }
  return TRUE;
}

static CharPtr CitOnFeatPrintProc (DoC doc, Int2 item, Pointer data)

{
  unsigned int  entityID;
  unsigned int  itemID;
  unsigned int  itemtype;
  CharPtr       rsult;
  CharPtr       str;

  rsult = NULL;
  if (data != NULL) {
    str = (CharPtr) data;
    if (sscanf (str, "%u %u %u", &entityID, &itemID, &itemtype) == 3) {
      GatherItem ((Uint2) entityID, (Uint2) itemID, (Uint2) itemtype,
                  (Pointer) &rsult, PrintCitOnFeatItem);
    }
  } else {
    rsult = StringSave ("Null Data\n");
  }
  return rsult;
}

static int LIBCALLBACK CompareCitList (VoidPtr ptr1, VoidPtr ptr2)

{
  CitListDataPtr  cldp1;
  CitListDataPtr  cldp2;
  CharPtr         str1;
  CharPtr         str2;

  if (ptr1 != NULL && ptr2 != NULL) {
    cldp1 = (CitListDataPtr) ptr1;
    cldp2 = (CitListDataPtr) ptr2;
    if (cldp1 != NULL && cldp2 != NULL) {
      str1 = cldp1->label;
      str2 = cldp2->label;
      if (str1 != NULL && str2 != NULL) {
        return StringICmp (str1, str2);
      } else {
        return 0;
      }
    } else {
      return 0;
    }
  } else {
    return 0;
  }
}

static Boolean MakeMinimalCitOnFeatItem (GatherContextPtr gcp)

{
  PubdescPtr  pdp;
  ValNodePtr  ppr;
  ValNodePtr  sdp;
  SeqFeatPtr  sfp;
  ValNodePtr  vnp;
  ValNodePtr  PNTR  vnpp;

  vnpp = (ValNodePtr PNTR) gcp->userdata;
  if (vnpp != NULL) {
    pdp = NULL;
    switch (gcp->thistype) {
      case OBJ_SEQDESC :
        sdp = (ValNodePtr) gcp->thisitem;
        if (sdp->data.ptrvalue != NULL) {
          pdp = (PubdescPtr) sdp->data.ptrvalue;
        }
        break;
      case OBJ_SEQFEAT :
        sfp = (SeqFeatPtr) gcp->thisitem;
        if (sfp != NULL && (sfp->data.choice == 10 || sfp->data.value.ptrvalue != NULL)) {
          pdp = (PubdescPtr) sfp->data.value.ptrvalue;
        }
        break;
      default :
        break;
    }
    if (pdp != NULL) {
      ppr = NULL;
      vnp = ValNodeNew (NULL);
      if (vnp != NULL) {
        vnp->choice = PUB_Equiv;
        vnp->data.ptrvalue = pdp->pub;
        ppr = MinimizePub (vnp);
        ValNodeFree (vnp);
      }
      vnp = ValNodeNew (*vnpp);
      if (*vnpp == NULL) {
        *vnpp = vnp;
      }
      if (vnp != NULL) {
        vnp->choice = PUB_Equiv;
        vnp->data.ptrvalue = ppr;
      }
    }
  }
  return TRUE;
}

static Boolean MatchMinimalCits (GatherContextPtr gcp)

{
  FeatCitEditPtr  fcep;
  PubdescPtr      pdp;
  ValNodePtr      ppr;
  ValNodePtr      sdp;
  SeqFeatPtr      sfp;
  ValNodePtr      vnp;

  fcep = (FeatCitEditPtr) gcp->userdata;
  if (fcep != NULL) {
    pdp = NULL;
    switch (gcp->thistype) {
      case OBJ_SEQDESC :
        sdp = (ValNodePtr) gcp->thisitem;
        if (sdp->data.ptrvalue != NULL) {
          pdp = (PubdescPtr) sdp->data.ptrvalue;
        }
        break;
      case OBJ_SEQFEAT :
        sfp = (SeqFeatPtr) gcp->thisitem;
        if (sfp != NULL && (sfp->data.choice == 10 || sfp->data.value.ptrvalue != NULL)) {
          pdp = (PubdescPtr) sfp->data.value.ptrvalue;
        }
        break;
      default :
        break;
    }
    if (pdp != NULL && fcep->psp != NULL) {
      vnp = ValNodeNew (NULL);
      if (vnp != NULL) {
        vnp->choice = PUB_Equiv;
        vnp->data.ptrvalue = pdp->pub;
        for (ppr = fcep->psp->data.ptrvalue; ppr != NULL; ppr = ppr->next) {
          if (PubLabelMatch (vnp, ppr) == 0) {
            fcep->chosen [fcep->index] = TRUE;
          }
        }
        ValNodeFree (vnp);
      }
    }
  }
  return TRUE;
}

static void CitListToDialog (DialoG d, Pointer userdata)
{
  FeatCitEditPtr  fcep;
  CitListDataPtr  cldp;
  CitListDataPtr  cldpp;
  Int2            count;
  GatherScope     gs;
  Int2            i;
  Int2            j;
  Char            last [128];
  CharPtr         ptr;
  RecT            r;
  Char            str [34];
  ValNodePtr      vnp;

  fcep = (FeatCitEditPtr) GetObjectExtra (d);
  if (fcep == NULL)
  {
    return;
  }
  Reset (fcep->allcitdoc);
  
  fcep->citlist = ValNodeFree (fcep->citlist);
  fcep->numitems = 0;
  fcep->chosen = MemFree (fcep->chosen);
  
  MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
  MemSet ((Pointer) (gs.ignore), (int) (TRUE), (size_t) (OBJ_MAX * sizeof (Boolean)));
  gs.ignore [OBJ_SEQDESC] = FALSE;
  gs.ignore [OBJ_SEQANNOT] = FALSE;
  gs.ignore [OBJ_SEQFEAT] = FALSE;
  gs.seglevels = 1;
  GatherEntity (fcep->entityID, (Pointer) fcep, GatherAllCits, &gs);
  count = fcep->numitems;
  cldpp = (CitListDataPtr) MemNew (sizeof (CitListData) * (size_t) (count + 1));
  if (cldpp != NULL) {
    for (i = 0, vnp = fcep->citlist; i < count && vnp != NULL; i++, vnp = vnp->next) {
      cldp = vnp->data.ptrvalue;
      if (cldp != NULL) {
        cldpp [i] = *cldp;
        cldp->label = NULL;
      }
    }
    fcep->citlist = ValNodeFreeData (fcep->citlist);
    HeapSort (cldpp, count, sizeof (CitListData), CompareCitList);
    last [0] = '\0';
    ObjectRect (fcep->allcitdoc, &r);
    InsetRect (&r, 4, 4);
    cofeColFmt.pixWidth = r.right - r.left;
    cofeColFmt.pixInset = 20;
    fcep->numitems = 0;
    fcep->chosen = MemNew (sizeof (Boolean) * (size_t) (count + 1));

    fcep->psp = (ValNodePtr) userdata;
    for (i = 0, j = 0; i < count; i++) {
      cldp = &(cldpp [i]);
      if (cldp != NULL) {
        if (last == NULL || StringCmp (cldp->label, last) != 0) {
          sprintf (str, "%d %d %d", (int) cldp->entityID,
                   (int) cldp->itemID, (int) cldp->itemtype);
          ptr = StringSave (str);
          (fcep->numitems)++;
          AppendItem (fcep->allcitdoc, CitOnFeatPrintProc, (Pointer) ptr,
                      TRUE, 5, &cofeParFmt, &cofeColFmt, programFont);
          if (fcep->chosen != NULL) {
            fcep->index = j;
            GatherItem ((Uint2) cldp->entityID, (Uint2) cldp->itemID,
                        (Uint2) cldp->itemtype, (Pointer) fcep, MatchMinimalCits);
          }
          j++;
        }
        StringNCpy_0 (last, cldp->label, sizeof (last));
        cldpp [i].label = MemFree (cldpp [i].label);
      }
    }
    fcep->psp = NULL;
  }
}

static Pointer DialogToMinimizedCitList (DialoG d)
{
  FeatCitEditPtr  fcep;
  Pointer         data;
  unsigned int    entityID;
  Int2            i;
  unsigned int    itemID;
  unsigned int    itemtype;
  Int2            numItems;
  ValNodePtr      ppr = NULL, psp = NULL;
  CharPtr         str;
  
  fcep = (FeatCitEditPtr) GetObjectExtra (d);
  if (fcep == NULL)
  {
    return NULL;
  }
  
  if (fcep->chosen != NULL) {
    GetDocParams (fcep->allcitdoc, &numItems, NULL);
    for (i = 1; i <= numItems; i++) {
      if (fcep->chosen [i - 1]) {
        GetItemParams (fcep->allcitdoc, i, NULL, NULL, NULL, NULL, &data);
        if (data != NULL) {
          str = (CharPtr) data;
          if (sscanf (str, "%u %u %u", &entityID, &itemID, &itemtype) == 3) {
            GatherItem ((Uint2) entityID, (Uint2) itemID, (Uint2) itemtype,
                        (Pointer) &ppr, MakeMinimalCitOnFeatItem);
          }
        }
      }
    }
  }
 
  if (ppr != NULL)
  {
    psp = ValNodeNew (NULL);
    if (psp != NULL) {
      psp->choice = 1;
      psp->data.ptrvalue = ppr;
    }
  }
  return psp;
}

extern DialoG FeatCitEditDialog (GrouP parent, Uint2 entityID)
{
  FeatCitEditPtr  fcep;
  GrouP           p;
  
  fcep = (FeatCitEditPtr) MemNew (sizeof (FeatCitEdit));
  if (fcep == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (parent, -1, 0, NULL);
  SetObjectExtra (p, fcep, CleanupFeatCitForm);

  fcep->dialog = (DialoG) p;
  fcep->todialog = CitListToDialog;
  fcep->fromdialog = DialogToMinimizedCitList;
  fcep->dialogmessage = NULL;
  fcep->testdialog = NULL;

  fcep->entityID = entityID;
  fcep->omp = ObjMgrGet ();
  fcep->numitems = 0;

  fcep->allcitdoc = DocumentPanel (p, 25 * stdCharWidth, 15 * stdLineHeight);
  SetObjectExtra (fcep->allcitdoc, fcep, NULL);
  SetDocProcs (fcep->allcitdoc, ClickFeatCit, NULL, ReleaseFeatCit, NULL);
  SetDocShade (fcep->allcitdoc, DrawFeatCit, NULL, NULL, NULL);
  
  SelectFont (programFont);
  fcep->lineheight = LineHeight ();
  SelectFont (systemFont);

  return (DialoG) p;
}

typedef struct featcitationform
{
  FeatureFormPtr ffp;
  DialoG         citation_list;
   
} FeatCitationFormData, PNTR FeatCitationFormPtr;

static void AcceptFeatCit (ButtoN b)

{
  FeatCitationFormPtr  fcfp;
  FeatureFormPtr       ffp;
  ValNodePtr           psp = NULL;

  fcfp = (FeatCitationFormPtr) GetObjectExtra (b);
  if (fcfp == NULL)
  {
    return;
  }
  
  psp = DialogToPointer (fcfp->citation_list);
  ffp = (FeatureFormPtr) fcfp->ffp;
  if (ffp != NULL) {
    PointerToDialog (ffp->featcits, (Pointer) psp);
    PubSetFree (psp);
  }
  Remove (ParentWindow (b));
}

static void EditFeatCitsProc (ButtoN b)

{
  ButtoN          btn;
  GrouP           c;
  FeatCitationFormPtr  fcfp;
  FeatureFormPtr  ffp;
  WindoW          w;
  ValNodePtr      psp;

  ffp = (FeatureFormPtr) GetObjectExtra (b);
  if (ffp == NULL)
  {
    return;
  }
  fcfp = (FeatCitationFormPtr) MemNew (sizeof (FeatCitationFormData));
  if (fcfp == NULL) 
  {
    return;
  }

  WatchCursor ();
  Update ();
  w = MovableModalWindow (-50, -33, -10, -10, "Citations", NULL);
  SetObjectExtra (w, fcfp, StdCleanupExtraProc);
  
  fcfp->ffp = ffp;
  fcfp->citation_list = FeatCitEditDialog (w, ffp->input_entityID);

  c = HiddenGroup (w, 4, 0, NULL);
  SetGroupSpacing (c, 10, 3);
  btn = PushButton (c, "Accept", AcceptFeatCit);
  SetObjectExtra (btn, fcfp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  AlignObjects (ALIGN_CENTER, (HANDLE) fcfp->citation_list, (HANDLE) c, NULL);
  RealizeWindow (w);
  
  psp = DialogToPointer (ffp->featcits);
  PointerToDialog (fcfp->citation_list, psp);
  PubSetFree (psp);

  Show (w);
  Select (w);
  ArrowCursor ();
  Update ();
}

static void ChangeGenePopupOrList (Handle gene)

{
  FeatureFormPtr  ffp;
  Int2            val;

  ffp = (FeatureFormPtr) GetObjectExtra (gene);
  if (ffp != NULL) {
    val = GetValue (ffp->gene);
    if (val == 1) {
      SafeHide (ffp->newGeneGrp);
      SafeHide (ffp->editGeneBtn);
    } else if (val == 2) {
      SafeHide (ffp->editGeneBtn);
      SafeShow (ffp->newGeneGrp);
    } else {
      SafeHide (ffp->newGeneGrp);
      SafeShow (ffp->editGeneBtn);
    }
  }
}

static void ChangeSubGroup (VoidPtr data, Int2 newval, Int2 oldval)

{
  FeatureFormPtr  ffp;

  ffp = (FeatureFormPtr) data;
  if (ffp != NULL) {
    if (ffp->commonPage >= 0 && ffp->commonPage <= 6) {
      SafeHide (ffp->commonSubGrp [ffp->commonPage]);
    }
    ffp->commonPage = newval;
    if (ffp->commonPage >= 0 && ffp->commonPage <= 6) {
      SafeShow (ffp->commonSubGrp [ffp->commonPage]);
    }
  }
}

typedef struct fieldpage {
  DIALOG_MESSAGE_BLOCK
  Int2               numfields;
  CharPtr            PNTR fields;
  TexT               PNTR values;
  ButtoN             PNTR boxes;
} FieldPage, PNTR FieldPagePtr;



static CharPtr TrimCommasAndAtSignsAroundGBString (CharPtr str)

{
  Uchar    ch;  /* to use 8bit characters in multibyte languages */
  CharPtr  dst;
  CharPtr  ptr;

  if (str != NULL && str [0] != '\0') {
    dst = str;
    ptr = str;
    ch = *ptr;
    while (ch != '\0' && (ch < ' ' || ch == '{' || ch == ',')) {
      ptr++;
      ch = *ptr;
    }
    while (ch != '\0') {
      *dst = ch;
      dst++;
      ptr++;
      ch = *ptr;
    }
    *dst = '\0';
    dst = NULL;
    ptr = str;
    ch = *ptr;
    while (ch != '\0') {
      if (ch != '}' && ch != ',') {
        dst = NULL;
      } else if (dst == NULL) {
        dst = ptr;
      }
      ptr++;
      ch = *ptr;
    }
    if (dst != NULL) {
      *dst = '\0';
    }
  }
  return str;
}

static CharPtr CombineSplitGBQual (CharPtr origval, CharPtr newval)

{
  size_t   len;
  CharPtr  str = NULL;

  if (StringStr (origval, newval) != NULL) return origval;
  len = StringLen (origval) + StringLen (newval) + 5;
  str = MemNew (sizeof (Char) * len);
  if (str == NULL) return origval;
  TrimCommasAndAtSignsAroundGBString (origval);
  TrimCommasAndAtSignsAroundGBString (newval);
  StringCpy (str, "{");
  StringCat (str, origval);
  StringCat (str, ",");
  StringCat (str, newval);
  StringCat (str, "}");
  /* free original string, knowing return value will replace it */
  MemFree (origval);
  return str;
}

static void CombineTitle (TexT t, CharPtr val)

{
  CharPtr  str;

  if (t == NULL || StringHasNoText (val)) return;
  str = SaveStringFromText (t);
  if (StringDoesHaveText (str)) {
    str = CombineSplitGBQual (str, val);
    SetTitle (t, str);
  } else {
    SetTitle (t, val);
  }
  MemFree (str);
}

static void GBQualPtrToFieldPage (DialoG d, Pointer data)

{
  Char          empty [4];
  FieldPagePtr  fpf;
  Int2          i;
  GBQualPtr     list;

  fpf = (FieldPagePtr) GetObjectExtra (d);
  list = (GBQualPtr) data;
  if (fpf != NULL) {
    while (list != NULL) {
      if (list->qual != NULL && list->val != NULL) {
        for (i = 0; i < fpf->numfields; i++) {
          if (StringICmp (list->qual, fpf->fields [i]) == 0) {
            if (fpf->values [i] != NULL) {
              if (*(list->val) == '\0') {
                empty [0] = '"';
                empty [1] = '"';
                empty [2] = '\0';
                SetTitle (fpf->values [i], empty);
              } else if (StringICmp (list->qual, "rpt_type") == 0 ||
                         StringICmp (list->qual, "rpt_unit") == 0 ||
                         StringICmp (list->qual, "rpt_unit_range") == 0 ||
                         StringICmp (list->qual, "rpt_unit_seq") == 0 ||
                         StringICmp (list->qual, "replace") == 0 ||
                         StringICmp (list->qual, "compare") == 0 ||
                         StringICmp (list->qual, "old_locus_tag") == 0 ||
                         StringICmp (list->qual, "usedin") == 0) {
                CombineTitle (fpf->values [i], list->val);
              } else {
                SetTitle (fpf->values [i], list->val);
              }
            } else if (fpf->boxes [i] != NULL) {
              SetStatus (fpf->boxes [i], TRUE);
            }
          }
        }
      }
      list = list->next;
    }
  }
}

extern void SeqFeatPtrToFieldPage (DialoG d, SeqFeatPtr sfp);
extern void SeqFeatPtrToFieldPage (DialoG d, SeqFeatPtr sfp)

{
  /*
  FieldPagePtr  fpf;
  Int2          i;

  fpf = (FieldPagePtr) GetObjectExtra (d);
  if (fpf != NULL && sfp != NULL) {
        for (i = 0; i < fpf->numfields; i++) {
          if (StringICmp ("exception", fpf->fields [i]) == 0) {
            if (fpf->values [i] != NULL) {
              if (sfp->except_text == NULL || *(sfp->except_text) == '\0') {
                SetTitle (fpf->values [i], "");
              } else {
                SetTitle (fpf->values [i], sfp->except_text);
              }
            }
          } else if (StringICmp ("pseudo", fpf->fields [i]) == 0) {
            if (fpf->boxes [i] != NULL && (! GetStatus (fpf->boxes [i]))) {
              SetStatus (fpf->boxes [i], sfp->pseudo);
            }
          }
        }
  }
  */

}

static Pointer FieldPageToGBQualPtr (DialoG d)

{
  FieldPagePtr  fpf;
  GBQualPtr     gbq;
  GBQualPtr     gbqlast;
  GBQualPtr     head;
  Int2          i;

  head = NULL;
  fpf = (FieldPagePtr) GetObjectExtra (d);
  if (fpf != NULL) {
    gbq = NULL;
    gbqlast = NULL;
    for (i = 0; i < fpf->numfields; i++) {
      if (fpf->fields [i] == NULL ||
          StringHasNoText (fpf->fields [i]) ||
          (fpf->values [i] == NULL && fpf->boxes [i] ==NULL) ||
          (fpf->values [i] != NULL && TextHasNoText (fpf->values [i]))) {
      } else if (fpf->boxes [i] != NULL && (! GetStatus (fpf->boxes [i]))) {
      } else {
        gbq = GBQualNew ();
        if (gbqlast == NULL) {
          head = gbq;
        } else {
          gbqlast->next = gbq;
        }
        gbqlast = gbq;
        if (gbq != NULL) {
          gbq->qual = StringSave (fpf->fields [i]);
          if (fpf->values [i] != NULL) {
            gbq->val = SaveStringFromText (fpf->values [i]);
          } else {
            gbq->val = StringSave ("");
          }
        }
      }
    }
  }
  return (Pointer) head;
}

static void CleanupFieldsPage (GraphiC g, VoidPtr data)

{
  FieldPagePtr  fpf;
  Int2          i;

  fpf = (FieldPagePtr) data;
  if (fpf != NULL) {
    if (fpf->fields != NULL) {
      for (i = 0; i < fpf->numfields; i++) {
        MemFree (fpf->fields [i]);
      }
    }
    MemFree (fpf->fields);
    MemFree (fpf->values);
    MemFree (fpf->boxes);
  }
  MemFree (data);
}

#define LEGAL_FEATURE    1
#define ILLEGAL_FEATURE  2

extern DialoG CreateImportFields (GrouP h, CharPtr name, SeqFeatPtr sfp, Boolean allowProductGBQual)

{
  FieldPagePtr    fpf;
  GrouP           g;
  GBQualPtr       gbq;
  Boolean         hasillegal;
  Int2            i;
  ImpFeatPtr      imp;
  Int2            index;
  Boolean         is_gap = FALSE;
  Int2            j;
  Int2            max;
  Int2            num;
  GrouP           p;
  PrompT          ppt;
  Int2            qual;
  Int2            seen [ParFlat_TOTAL_GBQUAL];
  SematicFeatPtr  sefp;
  Char            str [64];
  Int2            wid;

  p = HiddenGroup (h, 1, 0, NULL);
  fpf = (FieldPagePtr) MemNew (sizeof (FieldPage));
  if (fpf != NULL) {

    SetObjectExtra (p, fpf, CleanupFieldsPage);
    fpf->dialog = (DialoG) p;
    fpf->todialog = GBQualPtrToFieldPage;
    fpf->fromdialog = FieldPageToGBQualPtr;
    fpf->testdialog = NULL;

    if (sfp != NULL && sfp->data.choice == SEQFEAT_IMP) {
      imp = (ImpFeatPtr) sfp->data.value.ptrvalue;
      if (imp != NULL && StringICmp (imp->key, "gap") == 0) {
        is_gap = TRUE;
      }
    }

    gbq = NULL;
    if (sfp != NULL) {
      gbq = sfp->qual;
    }

    j = 0;
    hasillegal = FALSE;
    for (i = 0; i < ParFlat_TOTAL_GBQUAL; i++) {
      seen [i] = 0;
    }
    num = 0;

    if (name != NULL) {
      index = GBFeatKeyNameValid (&name, FALSE);
      if (index >= 0) {

        sefp = &(ParFlat_GBFeat [index]);

        for (i = 0; i < sefp->mand_num; i++) {
          qual = sefp->mand_qual [i];
          if (qual > -1 && ShouldBeAGBQual (sfp == NULL ? FEATDEF_ANY : sfp->idx.subtype, qual, allowProductGBQual)) {
            seen [qual] = LEGAL_FEATURE;
          }
        }
        if (StringCmp (name, "repeat_region") == 0) {
          seen [GBQUAL_map] = TRUE;
        }
        if (StringCmp (name, "operon") == 0) {
          seen [GBQUAL_operon] = TRUE;
        }
        for (i = 0; i < sefp->opt_num; i++) {
          qual = sefp->opt_qual [i];
          if (qual > -1 && ShouldBeAGBQual (sfp == NULL ? FEATDEF_ANY : sfp->idx.subtype, qual, allowProductGBQual)) {
            seen [qual] = LEGAL_FEATURE;
          }
        }
      }
    } else if (sfp != NULL && sfp->data.choice == SEQFEAT_CDREGION) {
      /*
      seen [GBQUAL_exception] = LEGAL_FEATURE;
      seen [GBQUAL_pseudo] = LEGAL_FEATURE;
      */
    }

    while (gbq != NULL) {
      qual = GBQualNameValid (gbq->qual);
      if (qual > -1) {
        if (seen [qual] == 0 && qual != GBQUAL_experiment && qual != GBQUAL_inference && qual != GBQUAL_pseudogene) {
          seen [qual] = ILLEGAL_FEATURE;
          hasillegal = TRUE;
        }
      }
      gbq = gbq->next;
    }

    SelectFont (programFont);
    max = 0;
    for (i = 0; i < ParFlat_TOTAL_GBQUAL; i++) {
      if (seen [i] > 0) {
        num++;
        if (seen [i] == LEGAL_FEATURE) {
          StringNCpy_0 (str, ParFlat_GBQual_names [i].name, sizeof (str));
          wid = StringWidth (str) + 2;
        } else {
          str [0] = '\0';
          if (name != NULL) {
            StringCpy (str, "*");
          }
          StringNCat (str, ParFlat_GBQual_names [i].name, sizeof (str) - 2);
          wid = StringWidth (str) + 2;
        }
        if (wid > max) {
          max = wid;
        }
      }
    }
    SelectFont (systemFont);

    fpf->numfields = num;
    fpf->fields = MemNew (sizeof (CharPtr) * (num + 1));
    fpf->values = MemNew (sizeof (TexT) * (num + 1));
    fpf->boxes = MemNew (sizeof (ButtoN) * (num + 1));

    g = HiddenGroup (p, 2, 0, NULL);
    j = 0;
    for (i = 0; i < ParFlat_TOTAL_GBQUAL; i++) {
      if (seen [i] == LEGAL_FEATURE) {
        fpf->fields [j] = StringSave (ParFlat_GBQual_names [i].name);
        ppt = StaticPrompt (g, fpf->fields [j], max, dialogTextHeight, programFont, 'l');
        if (ParFlat_GBQual_names [i].gbclass != Class_none) {
          fpf->values [j] = DialogText (g, "", 20, NULL);
          /*
          if (i == GBQUAL_estimated_length && is_gap) {
            Disable (fpf->values [j]);
          }
          */
        } else {
          fpf->boxes [j] = CheckBox (g, "", NULL);
          AlignObjects (ALIGN_MIDDLE, (HANDLE) ppt, (HANDLE) fpf->boxes [j], NULL);
        }
        j++;
      }
    }
    if (hasillegal && name != NULL) {
      StaticPrompt (p, "Illegal Qualifiers", 0, 0, programFont, 'c');
    }
    g = HiddenGroup (p, 2, 0, NULL);
    for (i = 0; i < ParFlat_TOTAL_GBQUAL; i++) {
      if (seen [i] == ILLEGAL_FEATURE) {
        fpf->fields [j] = StringSave (ParFlat_GBQual_names [i].name);
        str [0] = '\0';
        if (name != NULL) {
          StringCpy (str, "*");
        }
        StringNCat (str, fpf->fields [j], sizeof (str) - 2);
        ppt = StaticPrompt (g, str, max, dialogTextHeight, programFont, 'l');
        if (ParFlat_GBQual_names [i].gbclass != Class_none) {
          fpf->values [j] = DialogText (g, "", 20, NULL);
        } else {
          fpf->boxes [j] = CheckBox (g, "", NULL);
          AlignObjects (ALIGN_MIDDLE, (HANDLE) ppt, (HANDLE) fpf->boxes [j], NULL);
        }
        j++;
      }
    }

    if (j == 0) {
      StaticPrompt (p, "See Attributes page to set legal qualifiers for this feature.",
                    0, 0, programFont, 'c');
    }
  }
  return (DialoG) p;
}

static void ChangeCannedMessage (PopuP p)

{
  FeatureFormPtr  ffp;
  Int2            val;

  ffp = (FeatureFormPtr) GetObjectExtra (p);
  if (ffp == NULL) return;
  val = GetValue (p);
  switch (val) {
    case 1 :
      if (Message (MSG_YN, "Clear the explanation field?") == ANS_YES) {
        SetTitle (ffp->exceptText, "");
        if (Message (MSG_YN, "Clear the exception flag?") == ANS_YES) {
          SetStatus (ffp->exception, FALSE);
        }
      }
      break;
    case 2 :
      SetTitle (ffp->exceptText, "RNA editing");
      SetStatus (ffp->exception, TRUE);
      break;
    case 3 :
      SetTitle (ffp->exceptText, "reasons given in citation");
      SetStatus (ffp->exception, TRUE);
      break;
    case 4 :
      SetTitle (ffp->exceptText, "ribosomal slippage");
      SetStatus (ffp->exception, TRUE);
      break;
    case 5 :
      SetTitle (ffp->exceptText, "trans-splicing");
      SetStatus (ffp->exception, TRUE);
      break;
    case 6 :
      SetTitle (ffp->exceptText, "artificial frameshift");
      SetStatus (ffp->exception, TRUE);
      break;
    case 7 :
      SetTitle (ffp->exceptText, "nonconsensus splice site");
      SetStatus (ffp->exception, TRUE);
      break;
    case 8 :
      SetTitle (ffp->exceptText, "rearrangement required for product");
      SetStatus (ffp->exception, TRUE);
      break;
    case 9 :
      SetTitle (ffp->exceptText, "alternative start codon");
      SetStatus (ffp->exception, TRUE);
      break;
    case 10 :
      SetTitle (ffp->exceptText, "annotated by transcript or proteomic data");
      SetStatus (ffp->exception, TRUE);
      break;
    case 11 :
      SetTitle (ffp->exceptText, "heterogeneous population sequenced");
      SetStatus (ffp->exception, TRUE);
      break;
    case 12 :
      SetTitle (ffp->exceptText, "low-quality sequence region");
      SetStatus (ffp->exception, TRUE);
      break;
    case 13 :
      SetTitle (ffp->exceptText, "unextendable partial coding region");
      SetStatus (ffp->exception, TRUE);
      break;
    case 14 :
      SetTitle (ffp->exceptText, "translation initiation by tRNA-Leu at CUG codon");
      SetStatus (ffp->exception, TRUE);
      break;
    default :
      break;
  }
}

static CharPtr crossRefWarn =
"A gene mapped by cross-reference does not extend the range\n\
of the indicated gene feature.  Overlap is the usual case, and\n\
it does extend the selected gene.";

static CharPtr suppressWarn =
"This will suppress display of a gene qualifier even though\n\
there is a gene feature that overlaps this one.";

static void GeneXrefWarn (GrouP g)

{
  Int2  val;
  Boolean indexerVersion;

  indexerVersion = (Boolean) (GetAppProperty ("InternalNcbiSequin") != NULL);
  if (indexerVersion) return;

  val = GetValue (g);
  if (val == 2) {
    Message (MSG_OK, "%s", crossRefWarn);
  } else if (val == 3) {
    Message (MSG_OK, "%s", suppressWarn);
  }
}

static CharPtr  commonRadioFormTabs [] = {
  "General", "Comment", "Citations", "Cross-Refs", "Evidence", "Identifiers", NULL, NULL
};

static CharPtr  commonNoCitFormTabs [] = {
  "General", "Comment", "Cross-Refs", "Evidence", "Identifiers", NULL, NULL
};

static DialoG NewCreateInferenceDialog (GrouP prnt);
extern void Nlm_LaunchGeneFeatEd (ButtoN b);

static CharPtr GetNameForFeature (SeqFeatPtr sfp)
{
  FeatDefPtr curr;
  Uint1      key;
  CharPtr    label = NULL;
  CharPtr    featname = NULL;
  CharPtr    ptr;
  Char       ch;

  if (sfp != NULL) {
    curr = FeatDefFindNext (NULL, &key, &label, sfp->idx.subtype, TRUE);
    if (curr != NULL) {
      featname = StringSave (label);
      ptr = featname;
      ch = *ptr;
      while (ch != '\0') {
        if (IS_UPPER (ch)) {
          *ptr = TO_LOWER (ch);
        }
        ptr++;
        ch = *ptr;
      }
    }
  }
  return featname;
}


NLM_EXTERN void ClearTextBtn (ButtoN b)
{
  TexT t;

  t = (TexT) GetObjectExtra (b);
  SafeSetTitle (t, "");
}


extern GrouP CreateCommonFeatureGroupEx (GrouP h, FeatureFormPtr ffp,
                                         SeqFeatPtr sfp, Boolean hasGeneControl,
                                         Boolean hasCitationTab, Boolean hasGeneSuppress)

{
  ButtoN     b;
  GrouP      c;
  PopuP      canned;
  Boolean    cdsQuals;
  GrouP      f;
  GrouP      g;
  GBQualPtr  gbq;
  Boolean    hasQuals;
  Boolean    indexerVersion;
  Boolean    ispseudo = FALSE;
  Char       just;
  GrouP      k;
  GrouP      m;
  GrouP      p;
  Int2       page;
  PrompT     ppt1, ppt2;
  /*
  ButtoN     pseudo = NULL;
  */
  CharPtr    pseudogene = NULL;
  GrouP      q;
  GrouP      r;
  GrouP      t;
  GrouP      v;
  GrouP      x;
  GrouP      y;
  CharPtr    featname;

  c = NULL;
  if (ffp != NULL) {
    hasQuals = FALSE;
    cdsQuals = FALSE;
    if (sfp != NULL && sfp->pseudo) {
      ispseudo = TRUE;
    }
    if (ffp->gbquals == NULL && sfp != NULL && sfp->qual != NULL) {
      for (gbq = sfp->qual; gbq != NULL && !hasQuals; gbq = gbq->next) {
        if (StringCmp (gbq->qual, "pseudogene") == 0) {
          pseudogene = gbq->val;
        }
        if (!ShouldSuppressGBQual(sfp->idx.subtype, gbq->qual)) {
          hasQuals = TRUE;
        }
      }
      /*
      if (GetAppProperty ("InternalNcbiSequin") != NULL) {
        if (sfp->data.choice == SEQFEAT_CDREGION) {
          cdsQuals = TRUE;
        }
      }
      */
    }
    m = HiddenGroup (h, -1, 0, NULL);
    SetGroupSpacing (m, 10, 10);
    ffp->commonPage = 0;
    if (cdsQuals) {
    } else if (hasQuals) {
      commonRadioFormTabs [6] = "Qualifiers";
      commonNoCitFormTabs [5] = "Qualifiers";
    }
    if (hasCitationTab) {
      ffp->commonRadio = CreateFolderTabs (m, commonRadioFormTabs, ffp->commonPage,
                                           0, 0, PROGRAM_FOLDER_TAB,
                                           ChangeSubGroup, (Pointer) ffp);
    } else {
      ffp->commonRadio = CreateFolderTabs (m, commonNoCitFormTabs, ffp->commonPage,
                                           0, 0, PROGRAM_FOLDER_TAB,
                                           ChangeSubGroup, (Pointer) ffp);
    }
    commonRadioFormTabs [6] = NULL;
    commonNoCitFormTabs [5] = NULL;

    p = HiddenGroup (m, 0, 0, NULL);

    c = HiddenGroup (p, -1, 0, NULL);
    SetGroupSpacing (c, 10, 10);
    f = HiddenGroup (c, -1, 0, NULL);
    r = HiddenGroup (f, 7, 0, NULL);
    StaticPrompt (r, "Flags", 0, popupMenuHeight, programFont, 'l');
    ffp->partial = CheckBox (r, "Partial", NULL);
    indexerVersion = (Boolean) (GetAppProperty ("InternalNcbiSequin") != NULL);
    if (! indexerVersion) {
      Disable (ffp->partial);
    }
#if 0
    if (ffp->pseudo == NULL) {
      ffp->pseudo = CheckBox (r, "Pseudo", NULL);
      pseudo = ffp->pseudo; /* allows pseudo control on earlier feature-specific page */
    }
#endif
    StaticPrompt (r, "Pseudogene", 0, popupMenuHeight, programFont, 'l');
    ffp->pseudogene = PopupList (r, TRUE, NULL);
    SetObjectExtra (ffp->pseudogene, (Pointer) ffp, NULL);
    InitPseudogenePopup (ffp, ffp->pseudogene, ispseudo, pseudogene, indexerVersion);
    if (indexerVersion) {
      StaticPrompt (r, "Evidence", 0, popupMenuHeight, programFont, 'l');
      ffp->evidence = PopupList (r, TRUE, NULL);
      PopupItem (ffp->evidence, " ");
      PopupItem (ffp->evidence, "Experimental");
      PopupItem (ffp->evidence, "Non-Experimental");
    }
    AlignObjects (ALIGN_MIDDLE, (HANDLE) ffp->partial, /* (HANDLE) pseudo, */
                  (HANDLE) ffp->pseudogene, (HANDLE) ffp->evidence, NULL);
    r = HiddenGroup (f, -3, 0, NULL);
    ffp->exception = CheckBox (r, "Exception", NULL);
    StaticPrompt (r, "Explanation", 0, dialogTextHeight, programFont, 'l');
    ffp->exceptText = DialogText (r, "", 12, NULL);
    AlignObjects (ALIGN_MIDDLE, (HANDLE) ffp->exception,
                 (HANDLE) ffp->exceptText, NULL);
    if (ffp->this_subtype == FEATDEF_CDS) {
      StaticPrompt (r, "Standard explanation", 0, popupMenuHeight, programFont, 'l');
      canned = PopupList (r, TRUE, ChangeCannedMessage);
      SetObjectExtra (canned, (Pointer) ffp, NULL);
      PopupItem (canned, " ");
      PopupItem (canned, "RNA editing");
      PopupItem (canned, "reasons given in citation");
      PopupItem (canned, "ribosomal slippage");
      PopupItem (canned, "trans-splicing");
      PopupItem (canned, "artificial frameshift");
      PopupItem (canned, "nonconsensus splice site");
      PopupItem (canned, "rearrangement required");
      PopupItem (canned, "alternative start codon");
      PopupItem (canned, "transcript or proteome data");
      PopupItem (canned, "heterogeneous population");
      PopupItem (canned, "low-quality sequence region");
      PopupItem (canned, "unextendable partial CDS");
      if (indexerVersion) {
        PopupItem (canned, "Leu-CUG initiation");
      }
      if (sfp != NULL && sfp->excpt) {
        if (StringICmp (sfp->except_text, "RNA editing") == 0) {
          SetValue (canned, 2);
        } else if (StringICmp (sfp->except_text, "reasons given in citation") == 0 ||
                   StringICmp (sfp->except_text, "reasons cited in publication") == 0) {
          SetValue (canned, 3);
        } else if (StringICmp (sfp->except_text, "ribosomal slippage") == 0 ||
                   StringICmp (sfp->except_text, "ribosome slippage") == 0) {
          SetValue (canned, 4);
        } else if (StringICmp (sfp->except_text, "trans-splicing") == 0 ||
                   StringICmp (sfp->except_text, "trans splicing") == 0) {
          SetValue (canned, 5);
        } else if (StringICmp (sfp->except_text, "artificial frameshift") == 0) {
          SetValue (canned, 6);
        } else if (StringICmp (sfp->except_text, "non-consensus splice site") == 0 ||
                   StringICmp (sfp->except_text, "nonconsensus splice site") == 0) {
          SetValue (canned, 7);
        } else if (StringICmp (sfp->except_text, "rearrangement required for product") == 0) {
          SetValue (canned, 8);
        } else if (StringICmp (sfp->except_text, "alternative start codon") == 0) {
          SetValue (canned, 9);
        } else if (StringICmp (sfp->except_text, "annotated by transcript or proteomic data") == 0) {
          SetValue (canned, 10);
        } else if (StringICmp (sfp->except_text, "heterogeneous population sequenced") == 0) {
          SetValue (canned, 11);
        } else if (StringICmp (sfp->except_text, "low-quality sequence region") == 0) {
          SetValue (canned, 12);
        } else if (StringICmp (sfp->except_text, "unextendable partial coding region") == 0) {
          SetValue (canned, 13);
        } else if (indexerVersion && StringICmp (sfp->except_text, "translation initiation by tRNA-Leu at CUG codon") == 0) {
          SetValue (canned, 14);
        }
      } else {
        SetValue (canned, 1);
      }
    }

    if (cdsQuals) {
      /*
      ffp->gbquals = CreateImportFields (c, NULL, sfp, FALSE);
      */
    }

    g = NULL;
    k = NULL;
    ffp->gene = NULL;
    ffp->genePopup = NULL;
    ffp->geneList = NULL;
    ffp->geneNames = NULL;
    ffp->useGeneXref = NULL;
    ffp->newGeneGrp = NULL;
    ffp->geneSymbol = NULL;
    ffp->geneAllele = NULL;
    ffp->geneDesc = NULL;
    ffp->locusTag = NULL;
    ffp->geneSynonym = NULL;
    for (page = 0; page < 8; page++) {
      ffp->commonSubGrp [page] = NULL;
    }
    page = 0;

    if (hasGeneControl) {
      g = HiddenGroup (c, -2, 0, NULL);
      StaticPrompt (g, "Gene", 0, popupMenuHeight, programFont, 'l');
      v = HiddenGroup (g, 0, 0, NULL);
      ffp->genePopup = PopupList (v, TRUE, (PupActnProc) ChangeGenePopupOrList);
      SetObjectExtra (ffp->genePopup, (Pointer) ffp, NULL);
      PopupItem (ffp->genePopup, " ");
      PopupItem (ffp->genePopup, "New:");
      SetValue (ffp->genePopup, 1);
      Hide (ffp->genePopup);
      ffp->geneList = SingleList (v, 6, 3, (LstActnProc) ChangeGenePopupOrList);
      SetObjectExtra (ffp->geneList, (Pointer) ffp, NULL);
      ListItem (ffp->geneList, " ");
      ListItem (ffp->geneList, "New:");
      SetValue (ffp->geneList, 1);
      Hide (ffp->geneList);
      k = HiddenGroup (c, 3, 0, NULL);
      StaticPrompt (k, "Map by", 0, stdLineHeight, programFont, 'l');
      ffp->useGeneXref = HiddenGroup (k, 3, 0, GeneXrefWarn);
      SetObjectExtra (ffp->useGeneXref, ffp, NULL);
      RadioButton (ffp->useGeneXref, "Overlap");
      RadioButton (ffp->useGeneXref, "Cross-reference");
      RadioButton (ffp->useGeneXref, "Suppress");
      SetValue (ffp->useGeneXref, 1);
      y = HiddenGroup (c, 0, 0, NULL);
      ffp->newGeneGrp = HiddenGroup (y, 2, 0, NULL);
      StaticPrompt (ffp->newGeneGrp, "Gene Symbol", 0, dialogTextHeight, programFont, 'l');
      ffp->geneSymbol = DialogText (ffp->newGeneGrp, "", 20, NULL);
      StaticPrompt (ffp->newGeneGrp, "Allele", 0, dialogTextHeight, programFont, 'l');
      ffp->geneAllele = DialogText (ffp->newGeneGrp, "", 20, NULL);
      StaticPrompt (ffp->newGeneGrp, "Description", 0, dialogTextHeight, programFont, 'l');
      ffp->geneDesc = DialogText (ffp->newGeneGrp, "", 20, NULL);
      StaticPrompt (ffp->newGeneGrp, "Locus Tag", 0, dialogTextHeight, programFont, 'l');
      ffp->locusTag = DialogText (ffp->newGeneGrp, "", 20, NULL);
      StaticPrompt (ffp->newGeneGrp, "Synonym", 0, dialogTextHeight, programFont, 'l');
      ffp->geneSynonym = DialogText (ffp->newGeneGrp, "", 20, NULL);
      Hide (ffp->newGeneGrp);
      ffp->editGeneBtn = PushButton (y, "Edit Gene Feature", Nlm_LaunchGeneFeatEd);
      SetObjectExtra (ffp->editGeneBtn, ffp, NULL);
      Hide (ffp->editGeneBtn);
    } else if (hasGeneSuppress) {
      k = HiddenGroup (c, 3, 0, NULL);
      StaticPrompt (k, "Map by", 0, stdLineHeight, programFont, 'l');
      ffp->useGeneXref = HiddenGroup (k, 3, 0, GeneXrefWarn);
      SetObjectExtra (ffp->useGeneXref, ffp, NULL);
      RadioButton (ffp->useGeneXref, "Overlap");
      b = RadioButton (ffp->useGeneXref, "Cross-reference");
      Disable (b);
      RadioButton (ffp->useGeneXref, "Suppress");
      SetValue (ffp->useGeneXref, 1);
      y = HiddenGroup (c, 0, 0, NULL);
    }
    ffp->commonSubGrp [page] = c;
    page++;
    AlignObjects (ALIGN_CENTER, (HANDLE) f, (HANDLE) g,
                  (HANDLE) k, (HANDLE) ffp->newGeneGrp,
                  (HANDLE) ffp->editGeneBtn, NULL);

    c = HiddenGroup (p, -1, 0, NULL);
    SetGroupSpacing (c, 10, 10);
    q = HiddenGroup (c, 0, 3, NULL);
    StaticPrompt (q, "Comment", 25 * stdCharWidth, 0, programFont, 'c');
    if (GetAppProperty ("InternalNcbiSequin") != NULL) {
      ffp->comment = ScrollText (q, 25, 10, programFont, TRUE, NULL);
    } else {
      ffp->comment = ScrollText (q, 25, 5, programFont, TRUE, NULL);
    }
    b = PushButton (q, "Clear Comment", ClearTextBtn);
    SetObjectExtra (b, ffp->comment, NULL);
    ffp->commonSubGrp [page] = c;
    Hide (ffp->commonSubGrp [page]);
    page++;

    if (hasCitationTab) {
      c = HiddenGroup (p, -1, 0, NULL);
      SetGroupSpacing (c, 10, 10);
      ffp->featcits = CreateCitOnFeatDialog (c, NULL);
      b = PushButton (c, "Edit Citations", EditFeatCitsProc);
      SetObjectExtra (b, ffp, NULL);
      ffp->commonSubGrp [page] = c;
      AlignObjects (ALIGN_CENTER, (HANDLE) ffp->featcits, (HANDLE) b, NULL);
      Hide (ffp->commonSubGrp [page]);
      page++;
    }

    c = HiddenGroup (p, -1, 0, NULL);
    SetGroupSpacing (c, 10, 10);
    if (GetAppProperty ("ReadOnlyDbTags") == NULL) {
      just = 'c';
    } else {
      just = 'l';
      StaticPrompt (c, "This page is read-only", 15 * stdCharWidth, 0, programFont, 'c');
    }
    t = HiddenGroup (c, 2, 0, NULL);
    StaticPrompt (t, "Database", 7 * stdCharWidth, 0, programFont, just);
    StaticPrompt (t, "Object ID", 8 * stdCharWidth, 0, programFont, just);
    ffp->dbxrefs = CreateDbtagDialog (c, 3, -1, 7, 8);
    ffp->commonSubGrp [page] = c;
    Hide (ffp->commonSubGrp [page]);
    page++;

    c = HiddenGroup (p, -1, 0, NULL);
    SetGroupSpacing (c, 10, 10);
    q = HiddenGroup (c, 0, -6, NULL);
    ppt1 = StaticPrompt (q, "Experiment", 0, 0, programFont, 'c');
    ffp->experiment = CreateExperimentDialog (q);
    ppt2 = StaticPrompt (q, "Inference", 0, 0, programFont, 'c');
    /*
    ffp->inference = CreateInferenceDialog (q, 3, 2, 15);
    */
    ffp->inference = NewCreateInferenceDialog (q);
    AlignObjects (ALIGN_CENTER, (HANDLE) ppt1, (HANDLE) ffp->experiment,
                  (HANDLE) ppt2, (HANDLE) ffp->inference, NULL);
    ffp->commonSubGrp [page] = c;
    Hide (ffp->commonSubGrp [page]);
    page++;

    c = HiddenGroup (p, -1, 0, NULL);
    SetGroupSpacing (c, 10, 10);
    t = HiddenGroup (c, 2, 0, NULL);
    SetGroupSpacing (t, 10, 30);
    StaticPrompt (t, "Feature ID for this feature", 0, dialogTextHeight, programFont, 'l');
    ffp->featid = DialogText (t, "", 8, NULL);
    StaticPrompt (t, "ID Xref to associated feature", 0, dialogTextHeight, programFont, 'l');
    ffp->fidxref = DialogText (t, "", 8, NULL);
    if (! indexerVersion) {
      Disable (ffp->featid);
      Disable (ffp->fidxref);
    }
    ffp->commonSubGrp [page] = c;
    Hide (ffp->commonSubGrp [page]);
    page++;


    c = HiddenGroup (p, -1, 0, NULL);
    SetGroupSpacing (c, 10, 10);
    x = NULL;
    if (hasQuals && ffp->gbquals == NULL) {
      x = HiddenGroup (c, -1, 0, NULL);
      featname = GetGBFeatKeyForFeature (sfp);
      ffp->gbquals = NewCreateImportFields (x, featname, sfp, FALSE);
      featname = MemFree (featname);
    }
    ffp->commonSubGrp [page] = c;
    Hide (ffp->commonSubGrp [page]);
    page++;


    AlignObjects (ALIGN_CENTER, (HANDLE) ffp->commonRadio, (HANDLE) ffp->commonSubGrp [0],
                  (HANDLE) ffp->commonSubGrp [1], (HANDLE) ffp->commonSubGrp [2],
                  (HANDLE) ffp->commonSubGrp [3], (HANDLE) ffp->commonSubGrp [4],
                  (HANDLE) ffp->commonSubGrp [5], (HANDLE) ffp->commonSubGrp [6],
                  (HANDLE) ffp->commonSubGrp [7], NULL);
  }
  return c;
}

extern GrouP CreateCommonFeatureGroup (GrouP h, FeatureFormPtr ffp,
                                       SeqFeatPtr sfp, Boolean hasGeneControl,
                                       Boolean hasCitationTab)

{
  return CreateCommonFeatureGroupEx (h, ffp, sfp, hasGeneControl, hasCitationTab, FALSE);
}

static Boolean DlgutilFindBspItem (GatherContextPtr gcp)

{
  BioseqPtr  PNTR bspp;

  bspp = (BioseqPtr PNTR) gcp->userdata;
  if (bspp != NULL && gcp->thistype == OBJ_BIOSEQ) {
    *bspp = (BioseqPtr) gcp->thisitem;
  }
  return TRUE;
}

extern void SetNewFeatureDefaultInterval (FeatureFormPtr ffp)

{
  BioseqPtr     bsp;
  SelStructPtr  sel;
  SeqIntPtr     sip;
  SeqLocPtr     slp;

  if (ffp == NULL) return;
  bsp = NULL;
  GatherItem (ffp->input_entityID, ffp->input_itemID, ffp->input_itemtype,
              (Pointer) (&bsp), DlgutilFindBspItem);
  if (bsp == NULL) return;
  slp = NULL;
  sel = ObjMgrGetSelected ();
  if (sel != NULL && sel->next == NULL && sel->entityID == ffp->input_entityID &&
      sel->itemID == ffp->input_itemID && sel->itemtype == ffp->input_itemtype) {
    if (sel->regiontype == 1 && sel->region != NULL) {
      if (GetBioseqGivenSeqLoc ((SeqLocPtr) sel->region, ffp->input_entityID) == bsp) {
        slp = AsnIoMemCopy (sel->region,
                            (AsnReadFunc) SeqLocAsnRead,
                            (AsnWriteFunc) SeqLocAsnWrite);
      }
    }
  }
  if (slp == NULL) {
    slp = ValNodeNew (NULL);
    if (slp == NULL) return;
    sip = SeqIntNew ();
    if (sip == NULL) {
      slp = SeqLocFree (slp);
      return;
    }
    slp->choice = SEQLOC_INT;
    slp->data.ptrvalue = (Pointer) sip;
    sip->from = 0;
    sip->to = bsp->length - 1;
    sip->strand = Seq_strand_plus;
    sip->id = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (bsp->id, 0)));
  }
  if (slp != NULL) {
    PointerToDialog (ffp->location, (Pointer) slp);
    SeqLocFree (slp);
  }
}

extern Boolean FileToScrollText (TexT t, CharPtr path)

{
  FILE     *fp;
  Int8     len;
  Int4     read_len;
  Int4     max;
  CharPtr  str;
#ifdef WIN_MAC
  CharPtr  p;
#endif
#if (defined(OS_DOS) || defined (OS_NT))
  CharPtr  p;
  CharPtr  q;
#endif

  if (t != NULL && path != NULL && *path != '\0') {
    len = FileLength (path);
    max = (Int4) INT2_MAX;
#ifdef WIN_MOTIF
    max = INT4_MAX;
#endif
#ifdef WIN_MSWIN
    max = INT4_MAX;
#endif
#ifdef WIN_MAC
#ifdef OS_UNIX_DARWIN
    max = INT4_MAX;
#endif
#endif
    if (len > 0 && len < max - 4) {
      str = MemNew (sizeof (char) * ((size_t)len + 3));
      if (str != NULL) {
        fp = FileOpen (path, "r");
        if (fp != NULL) {
          read_len = FileRead (str, sizeof (char), (size_t) len, fp);
          str [ read_len ] = 0;
#if (defined(OS_DOS) || defined (OS_NT))
          p = str;
          q = str;
          while (*p) {
            if (*p == '\r') {
              p++;
            } else {
              *q = *p;
              p++;
              q++;
            }
          }
          *q = '\0';
#endif
#ifdef WIN_MAC
          p = str;
          while (*p) {
            if (*p == '\r' || *p == '\n') {
              *p = '\015';
            }
            p++;
          }
#endif
          FileClose (fp);
          SetTitle (t, str);
        }
        MemFree (str);
        return TRUE;
      }
    }
  }
  return FALSE;
}

extern void ScrollTextToFile (TexT t, CharPtr path)

{
  FILE     *fp;
  size_t   len;
  CharPtr  str;
#ifdef WIN_MAC
  CharPtr  p;
#endif

  if (t != NULL && path != NULL && *path != '\0') {
    len = TextLength (t);
    if (len > 0) {
#ifdef WIN_MAC
      fp = FileOpen (path, "r");
      if (fp != NULL) {
        FileClose (fp);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      fp = FileOpen (path, "w");
      if (fp != NULL) {
        str = MemNew (sizeof (char) * (len + 3));
        if (str != NULL) {
          GetTitle (t, str, len + 1);
#ifdef WIN_MAC
          p = str;
          while (*p) {
            if (*p == '\r' || *p == '\n') {
              *p = '\n';
            }
            p++;
          }
#endif
          FileWrite (str, sizeof (char), len, fp);
          MemFree (str);
        }
        FileClose (fp);
      }
    }
  }
}

extern void FileToClipboard (CharPtr path)

{
  FILE     *fp;
  Int8     len;
  Int4     max;
  CharPtr  str;
#ifdef WIN_MAC
  CharPtr  p;
#endif
#if (defined(OS_DOS) || defined (OS_NT))
  CharPtr  p;
  CharPtr  q;
#endif

  if (path != NULL && *path != '\0') {
    len = FileLength (path);
#ifdef WIN_MOTIF
    max = INT4_MAX;
#else
    max = (Int4) INT2_MAX;
#endif
    if (len > 0 && len < max - 4) {
      str = MemNew (sizeof (char) * ((size_t)len + 3));
      if (str != NULL) {
        fp = FileOpen (path, "r");
        if (fp != NULL) {
          FileRead (str, sizeof (char), (size_t) len, fp);
#if (defined(OS_DOS) || defined (OS_NT))
          p = str;
          q = str;
          while (*p) {
            if (*p == '\r') {
              p++;
            } else {
              *q = *p;
              p++;
              q++;
            }
          }
          *q = '\0';
#endif
#ifdef WIN_MAC
          p = str;
          while (*p) {
            if (*p == '\r' || *p == '\n') {
              *p = '\015';
            }
            p++;
          }
#endif
          FileClose (fp);
          Nlm_StringToClipboard (str);
        }
        MemFree (str);
      }
    }
  }
}

typedef struct textviewform {
  FORM_MESSAGE_BLOCK

  DoC              doc;
  TexT             text;

  TexT             find;
} TextViewForm, PNTR TextViewFormPtr;

static ParData txtParFmt = {FALSE, FALSE, FALSE, FALSE, TRUE, 0, 0};
static ColData txtColFmt = {0, 0, 80, 0, NULL, 'l', FALSE, FALSE, FALSE, FALSE, TRUE};

static void ResizeTextViewer (WindoW w)

{
  Int2             height;
  RecT             r;
  RecT             s;
  TextViewFormPtr  tfp;
  Int2             width;

  tfp = (TextViewFormPtr) GetObjectExtra (w);
  if (tfp != NULL) {
    WatchCursor ();
    ObjectRect (w, &r);
    width = r.right - r.left;
    height = r.bottom - r.top;
    if (tfp->doc != NULL) {
      GetPosition (tfp->doc, &s);
      s.right = width - s.left;
      s.bottom = height - s.left;
      SetPosition (tfp->doc, &s);
      AdjustPrnt (tfp->doc, &s, FALSE);
      txtColFmt.pixWidth = screenRect.right - screenRect.left;
      txtColFmt.pixInset = 8;
      if (Visible (tfp->doc) && AllParentsVisible (tfp->doc)) {
        UpdateDocument (tfp->doc, 0, 0);
      }
    }
    if (tfp->text != NULL) {
      GetPosition (tfp->text, &s);
      s.right = width - s.left;
      s.bottom = height - s.left;
      SetPosition (tfp->text, &s);
      AdjustPrnt (tfp->text, &s, FALSE);
    }
    ArrowCursor ();
    Update ();
  }
}

static Boolean ExportTextViewForm (ForM f, CharPtr filename)

{
  FILE             *fp;
  Char             path [PATH_MAX];
  TextViewFormPtr  tfp;

  tfp = (TextViewFormPtr) GetObjectExtra (f);
  if (tfp != NULL && (tfp->doc != NULL || tfp->text != NULL)) {
    path [0] = '\0';
    StringNCpy_0 (path, filename, sizeof (path));
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      fp = FileOpen (path, "r");
      if (fp != NULL) {
        FileClose (fp);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      if (tfp->doc != NULL) {
        fp = FileOpen (path, "w");
        if (fp != NULL) {
          SaveDocument (tfp->doc, fp);
          FileClose (fp);
          return TRUE;
        }
      } else if (tfp->text != NULL) {
        ScrollTextToFile (tfp->text, path);
        return TRUE;
      }
    }
  }
  return FALSE;
}

static void LIBCALL PrintTextViewForm (Pointer formDataPtr)

{
  TextViewFormPtr  tfp;

  tfp = (TextViewFormPtr) formDataPtr;
  if (tfp != NULL && tfp->doc != NULL) {
    PrintDocument (tfp->doc);
  }
}

static void TextViewFormMessage (ForM f, Int2 mssg)

{
  TextViewFormPtr  tfp;

  tfp = (TextViewFormPtr) GetObjectExtra (f);
  if (tfp != NULL) {
    switch (mssg) {
      case VIB_MSG_EXPORT :
        ExportTextViewForm (f, NULL);
        break;
      case VIB_MSG_CLOSE :
        Remove (f);
        break;
      case VIB_MSG_PRINT :
        PrintTextViewForm (tfp);
        break;
      case VIB_MSG_CUT :
        StdCutTextProc (NULL);
        break;
      case VIB_MSG_COPY :
        StdCopyTextProc (NULL);
        break;
      case VIB_MSG_PASTE :
        StdPasteTextProc (NULL);
        break;
      case VIB_MSG_DELETE :
        StdDeleteTextProc (NULL);
        break;
      default :
        if (tfp->appmessage != NULL) {
          tfp->appmessage (f, mssg);
        }
        break;
    }
  }
}


static void FindInGeneralText (ButtoN b)

{
  Int2             actual;
  Char             buf [1030];
  Char             ch;
  Int2             cnt;
  Int8             cntr;
  Int2             first;
  FILE             *fp;
  Char             lastch;
  Int4             line = 0;
  ValNodePtr       matches;
  Int2             next;
  Int2             offset = 0;
  Char             path [PATH_MAX];
  CharPtr          ptr;
  Int4             state;
  CharPtr          str;
  TextFsaPtr       tbl;
  TextViewFormPtr  tfp;
  Int4             max;

  tfp = (TextViewFormPtr) GetObjectExtra (b);
  if (tfp == NULL) return;
  if (tfp->doc != NULL) {
    GetOffset (tfp->doc, NULL, &offset);
  } else if (tfp->text != NULL) {
    GetOffset (tfp->text, NULL, &offset);
  }
  first = -1;
  next = -1;
  max = INT2_MAX;

  str = SaveStringFromText (tfp->find);

  if (StringDoesHaveText (str)) {
    TmpNam (path);
    if (ExportForm (tfp->form, path)) {
      tbl = TextFsaNew ();
      if (tbl != NULL) {
        TextFsaAdd (tbl, str);
        fp = FileOpen (path, "r");
        if (fp != NULL) {
          line = 0;
          state = 0;
          cntr = FileLength (path);
          cnt = (Int2) MIN (cntr, 1024L);
          lastch = '\0';
          while (cnt > 0 && cntr > 0 && line <= max && 
                 ((next == -1 && offset > first) || first == -1)) {
            actual = (Int2) FileRead (buf, 1, cnt, fp);
            if (actual > 0) {
              cnt = actual;
              buf [cnt] = '\0';
              ptr = buf;
              ch = *ptr;
              while (ch != '\0') {
                if (ch == '\n' || ch == '\r') {
                  if (ch == '\n' && lastch == '\r') {
                    /* do not increment line */
                  } else if (ch == '\r' && lastch == '\n') {
                    /* do not increment line */
                  } else {
                    line++;
                  }
                }
                state = TextFsaNext (tbl, state, ch, &matches);
                if (matches != NULL) {
                  if (first == -1) {
                    first = line;
                  }
                  if (next == -1 && line > offset) {
                    next = line;
                  }
                }
                lastch = ch;
                ptr++;
                ch = *ptr;
              }
              cntr -= cnt;
              cnt = (Int2) MIN (cntr, 1024L);
            } else {
              cnt = 0;
              cntr = 0;
            }
          }
        }
        FileClose (fp);
      }
      TextFsaFree (tbl);
    }
    FileRemove (path);
  }
  MemFree (str);
  if (line > max) {
    Message (MSG_ERROR, "Too many lines for search");
  }

  if (next >= 0) {
    offset = next;
  } else if (first >= 0) {
    offset = first;
  } else return;
  if (tfp->doc != NULL) {
    SetOffset (tfp->doc, 0, offset);
    Update ();
  } else if (tfp->text != NULL) {
    SetOffset (tfp->text, 0, offset);
    Update ();
  }
}

typedef struct repopulateviewer
{
  Nlm_RepopulateViewer   repopulate_func; 
  Pointer                repopulate_data;
  Nlm_RepopulateDataFree free_data_func;
  TextViewFormPtr        tfp;
  FonT                   fnt;
} RepopulateViewerData, PNTR RepopulateViewerPtr;

static void CleanupRepopulateViewer (Nlm_GraphiC g, Nlm_VoidPtr data)
{
  RepopulateViewerPtr rp;
  
  rp = (RepopulateViewerPtr) data;
  if (rp != NULL && rp->free_data_func != NULL)
  {
    (rp->free_data_func)(rp->repopulate_data);
  }
  
  StdCleanupExtraProc (g, data);
}

static void RepopulateViewer (ButtoN b)
{
  RepopulateViewerPtr rp;
  CharPtr             new_path;
  
  rp = (RepopulateViewerPtr) GetObjectExtra (b);
  
  if (rp == NULL || rp->repopulate_func == NULL)
  {
    return;
  }
  
  new_path = (rp->repopulate_func) (rp->repopulate_data);
  if (new_path == NULL)
  {
    return;
  }
  
  if (rp->tfp->text != NULL)
  {
    FileToScrollText (rp->tfp->text, new_path);
  }
  else if (rp->tfp->doc != NULL)
  {
    txtColFmt.pixWidth = screenRect.right - screenRect.left;
    txtColFmt.pixInset = 8;
    DisplayFancy (rp->tfp->doc, new_path, &txtParFmt, &txtColFmt, rp->fnt, 0);
  }
  FileRemove (new_path);
  new_path = MemFree (new_path);
}

static void 
LaunchGeneralTextViewerEx 
(CharPtr path,
 CharPtr title, 
 Boolean useScrollText,
 Nlm_RepopulateViewer   repopulate_func, 
 Pointer                repopulate_data,
 Nlm_RepopulateDataFree free_data_func)

{
  ButtoN              b;
  FonT                fnt;
  GrouP               g;
  Int2                pixheight;
  Int2                pixwidth;
  StdEditorProcsPtr   sepp;
  TextViewFormPtr     tfp;
  TextViewProcsPtr    tvpp;
  RepopulateViewerPtr rp;
  WindoW              w;
#ifndef WIN_MAC
  MenU                m;
#endif

  tfp = (TextViewFormPtr) MemNew (sizeof (TextViewForm));
  if (tfp == NULL) return;

  w = DocumentWindow (-50, -33, -10, -10, title, StdCloseWindowProc, ResizeTextViewer);
  SetObjectExtra (w, tfp, StdCleanupFormProc);
  tfp->form = (ForM) w;
  tfp->exportform = ExportTextViewForm;
  tfp->formmessage = TextViewFormMessage;

  sepp = (StdEditorProcsPtr) GetAppProperty ("StdEditorForm");
  if (sepp != NULL) {
    SetActivate (w, sepp->activateForm);
    tfp->appmessage = sepp->handleMessages;
  }

  fnt = programFont;
  pixwidth = 35 * stdCharWidth + 17;
  pixheight = 20 * stdLineHeight;

  tvpp = (TextViewProcsPtr) GetAppProperty ("TextDisplayForm");
  if (tvpp != NULL) {
    pixwidth = MAX (pixwidth, tvpp->minPixelWidth);
    pixheight = MAX (pixheight, tvpp->minPixelHeight);
    if (tvpp->displayFont != NULL) {
      fnt = tvpp->displayFont;
    }
    if (tvpp->activateForm != NULL) {
      SetActivate (w, tvpp->activateForm);
    }
  }

#ifndef WIN_MAC
  m = PulldownMenu (w, "File");
  FormCommandItem (m, "Export...", (BaseFormPtr) tfp, VIB_MSG_EXPORT);
  SeparatorItem (m);
  FormCommandItem (m, "Close", (BaseFormPtr) tfp, VIB_MSG_CLOSE);
  if (tvpp != NULL && tvpp->useScrollText) {
    m = PulldownMenu (w, "Edit");
    FormCommandItem (m, CUT_MENU_ITEM, (BaseFormPtr) tfp, VIB_MSG_CUT);
    FormCommandItem (m, COPY_MENU_ITEM, (BaseFormPtr) tfp, VIB_MSG_COPY);
    FormCommandItem (m, PASTE_MENU_ITEM, (BaseFormPtr) tfp, VIB_MSG_PASTE);
    FormCommandItem (m, CLEAR_MENU_ITEM, (BaseFormPtr) tfp, VIB_MSG_DELETE);
  }
#endif

#ifdef WIN_MAC
  if (useScrollText) {
    if (FileLength (path) > 32767) {
      /* text edit window has maximum length on Mac */
      useScrollText = FALSE;
    }
  }
#endif

  /* right now Find button is only in indexer Sequin */
  if (useScrollText) {
    g = HiddenGroup (w, 5, 0, NULL);
    b = PushButton (g, "Find", FindInGeneralText);
    SetObjectExtra (b, tfp, NULL);
    tfp->find = DialogText (g, "", 10, NULL);
    if (repopulate_func != NULL)
    {
      rp = (RepopulateViewerPtr) MemNew (sizeof (RepopulateViewerData));
      if (rp != NULL)
      {
        rp->repopulate_func = repopulate_func;
        rp->repopulate_data = repopulate_data;
        rp->free_data_func = free_data_func;
        rp->tfp = tfp;
        rp->fnt = fnt;
        b = PushButton (g, "Repopulate", RepopulateViewer);
        SetObjectExtra (b, rp, CleanupRepopulateViewer);
      }
    }
  }

  if (useScrollText) {
    tfp->text = ScrollText (w, (pixwidth + stdCharWidth - 1) / stdCharWidth,
                            (pixheight + stdLineHeight - 1) / stdLineHeight,
                            fnt, FALSE, NULL);
    SetObjectExtra (tfp->text, tfp, NULL);
    RealizeWindow (w);
    if (! FileToScrollText (tfp->text, path)) {
      /* SetTitle (tfp->text, "(Text is too large to be displayed in this control.)"); */
      Remove (w);
      LaunchGeneralTextViewerEx (path, title, FALSE,
                                 repopulate_func, repopulate_data, free_data_func);
      return;
    }
  } else {
    tfp->doc = DocumentPanel (w, pixwidth, pixheight);
    SetObjectExtra (tfp->doc, tfp, NULL);
    RealizeWindow (w);
    txtColFmt.pixWidth = screenRect.right - screenRect.left;
    txtColFmt.pixInset = 8;
    DisplayFancy (tfp->doc, path, &txtParFmt, &txtColFmt, fnt, 0);
    /* document.c: SaveTableItem does not strip preceeding tabs if tabCount is 0 */
  }
  Show (w);
  Select (w);
  Update ();
}

extern void LaunchGeneralTextViewerWithRepopulate 
(CharPtr                path,
 CharPtr                title, 
 Nlm_RepopulateViewer   repopulate_func,
 Pointer                repopulate_data,
 Nlm_RepopulateDataFree free_data_func)
{
  TextViewProcsPtr tvpp;

  tvpp = (TextViewProcsPtr) GetAppProperty ("TextDisplayForm");
  if (tvpp != NULL && tvpp->useScrollText) {
    LaunchGeneralTextViewerEx (path, title, TRUE, 
                               repopulate_func, repopulate_data, free_data_func);
  } else {
    LaunchGeneralTextViewerEx (path, title, FALSE, 
                               repopulate_func, repopulate_data, free_data_func);
  }
}

extern void LaunchGeneralTextViewer (CharPtr path, CharPtr title)

{
  TextViewProcsPtr tvpp;

  tvpp = (TextViewProcsPtr) GetAppProperty ("TextDisplayForm");
  if (tvpp != NULL && tvpp->useScrollText) {
    LaunchGeneralTextViewerEx (path, title, TRUE, NULL, NULL, NULL);
  } else {
    LaunchGeneralTextViewerEx (path, title, FALSE, NULL, NULL, NULL);
  }
}

extern void LaunchAsnTextViewer (Pointer from, AsnWriteFunc writefunc, CharPtr title)

{
  AsnIoPtr  aip;
  Char      path [PATH_MAX];

  if (from == NULL || writefunc == NULL) return;
  if (StringHasNoText (title)) {
    title = "General ASN.1 Text Viewer";
  }

  TmpNam (path);
  aip = AsnIoOpen (path, "w");
  if (aip != NULL) {
    (*writefunc) (from, aip, NULL);
    AsnIoClose (aip);
    LaunchGeneralTextViewer (path, title);
  }
  FileRemove (path);
}

#ifndef WIN_MAC
extern void CreateStdEditorFormMenus (WindoW w)

{
  BaseFormPtr   bfp;
  MenU          m;

  bfp = (BaseFormPtr) GetObjectExtra (w);
  if (bfp != NULL) {
    m = PulldownMenu (w, "File");
    if (bfp->importform != NULL || bfp->exportform != NULL) {
      if (bfp->importform != NULL) {
        FormCommandItem (m, "Import...", bfp, VIB_MSG_IMPORT);
      }
      if (bfp->exportform != NULL) {
        FormCommandItem (m, "Export...", bfp, VIB_MSG_EXPORT);
      }
      SeparatorItem (m);
    }
    FormCommandItem (m, "Close", bfp, VIB_MSG_CLOSE);
    m = PulldownMenu (w, "Edit");
    FormCommandItem (m, CUT_MENU_ITEM, bfp, VIB_MSG_CUT);
    FormCommandItem (m, COPY_MENU_ITEM, bfp, VIB_MSG_COPY);
    FormCommandItem (m, PASTE_MENU_ITEM, bfp, VIB_MSG_PASTE);
    FormCommandItem (m, CLEAR_MENU_ITEM, bfp, VIB_MSG_DELETE);
  }
}
#endif

static Boolean DlgutilGetLowestStackSeqEntry (GatherContextPtr gcp)

{
  BaseFormPtr  bfp;
  Int2         i;

  if (gcp == NULL) return TRUE;
  bfp = (BaseFormPtr) gcp->userdata;
  if (bfp == NULL) return TRUE;
  if (gcp->gatherstack != NULL && gcp->numstack > 0) {
    for (i = 0; i < gcp->numstack; i++) {
      if (gcp->gatherstack [i].itemtype == OBJ_BIOSEQ ||
          gcp->gatherstack [i].itemtype == OBJ_BIOSEQSET) {
        bfp->input_itemID = gcp->gatherstack [i].itemID;
        bfp->input_itemtype = gcp->gatherstack [i].itemtype;
      }
    }
  }
  return FALSE;
}

extern Boolean SetClosestParentIfDuplicating (BaseFormPtr bfp)

{
  Uint4              itemID;
  Uint2              itemtype;
  StdEditorProcsPtr  sepp;

  sepp = (StdEditorProcsPtr) GetAppProperty ("StdEditorForm");
  if (bfp == NULL || sepp == NULL || (! sepp->duplicateExisting)) return FALSE;
  itemID = bfp->input_itemID;
  itemtype = bfp->input_itemtype;
  GatherItem (bfp->input_entityID, bfp->input_itemID, bfp->input_itemtype,
              (Pointer) bfp, DlgutilGetLowestStackSeqEntry);
  if (itemID == bfp->input_itemID && itemtype == bfp->input_itemtype) {
    return FALSE;
  }
  return TRUE;
}

/*****************************************************************************
*
*   Bond and Point SeqLoc dialogs
*
*****************************************************************************/

typedef struct pointpage {
  DIALOG_MESSAGE_BLOCK
  TexT               point;
  Int2               count;
  SeqEntryPtr        PNTR bsptr;
  EnumFieldAssoc     PNTR alist;
  PopuP              strand;
  PopuP              seqIdx;
  Boolean            nucsOK;
  Boolean            protsOK;
  Boolean            showIdTags;
} PointPage, PNTR PointPagePtr;

typedef struct bondpage {
  DIALOG_MESSAGE_BLOCK
  DialoG             pointA;
  DialoG             pointB;
} BondPage, PNTR BondPagePtr;

static void FillInPointProducts (SeqEntryPtr sep, Pointer mydata,
                                 Int4 index, Int2 indent)

{
  BioseqPtr     bsp;
  PointPagePtr  ppp;

  if (sep != NULL && mydata != NULL && sep->choice == 1) {
    ppp = (PointPagePtr) mydata;
    bsp = (BioseqPtr) sep->data.ptrvalue;
    if (bsp != NULL) {
      if ((ppp->nucsOK && ISA_na (bsp->mol)) ||
          (ppp->protsOK && ISA_aa (bsp->mol))) {
        ppp->count++;
        ppp->bsptr [ppp->count] = sep;
      }
    }
  }
}

static ENUM_ALIST(strand_alist)
{" ",             Seq_strand_unknown},  /* 0 */
{"Plus",          Seq_strand_plus},     /* 1 */
{"Minus",         Seq_strand_minus},    /* 2 */
/*
{"Both",          Seq_strand_both},
{"Reverse",       Seq_strand_both_rev},
*/
{"Other",         Seq_strand_other},    /* 255 */
END_ENUM_ALIST

static void SeqPntPtrToPointPage (DialoG d, Pointer data)


{
  BioseqPtr     bsp;
  Int2          j;
  PointPagePtr  ppp;
  SeqEntryPtr   sep;
  Int2          seq;
  SeqPntPtr     spp;
  Char          str [16];
  Uint1         strand;

  ppp = (PointPagePtr) GetObjectExtra (d);
  if (ppp == NULL) return;
  spp = (SeqPntPtr) data;
  if (spp != NULL) {
    sprintf (str, "%ld", (long) (spp->point + 1));
    SafeSetTitle (ppp->point, str);
    seq = 0;
    strand = 0;
    bsp = BioseqFind (spp->id);
    if (bsp != NULL) {
      strand = spp->strand;
      if (strand > Seq_strand_both_rev && strand != Seq_strand_other) {
        strand = Seq_strand_unknown;
      }
      if (ppp->bsptr != NULL) {
        for (j = 1; j <= ppp->count && seq == 0; j++) {
          sep = ppp->bsptr [j];
          if (sep != NULL && sep->choice == 1) {
            if (bsp == (BioseqPtr) sep->data.ptrvalue) {
              seq = j;
            }
          }
        }
      }
    }
    SetEnumPopup (ppp->strand, strand_alist, (UIEnum) strand);
    SetEnumPopup (ppp->seqIdx, ppp->alist, (UIEnum) seq);
  } else {
    SafeSetTitle (ppp->point, "");
    SafeSetValue (ppp->strand, 0);
    SafeSetValue (ppp->seqIdx, 0);
  }
}

static Pointer PointPageToSeqPntPtr (DialoG d)

{
  BioseqPtr     bsp;
  PointPagePtr  ppp;
  SeqEntryPtr   sep;
  UIEnum        seq;
  SeqPntPtr     spp;
  Char          str [16];
  UIEnum        strand;
  Int4          val;

  ppp = (PointPagePtr) GetObjectExtra (d);
  if (ppp == NULL) return NULL;
  spp = NULL;
  GetTitle (ppp->point, str, sizeof (str) - 1);
  if (StrToLong (str, &val) && val > 0) {
    if (GetEnumPopup (ppp->seqIdx, ppp->alist, &seq) &&
        seq > 0 && seq <= ppp->count) {
      spp = SeqPntNew ();
      if (spp != NULL) {
        spp->point = val - 1;
        if (GetEnumPopup (ppp->strand, strand_alist, &strand)) {
          spp->strand = (Uint1) strand;
        }
        sep = ppp->bsptr [(Int2) seq];
        if (sep != NULL && sep->choice == 1) {
          bsp = (BioseqPtr) sep->data.ptrvalue;
          if (bsp != NULL) {
            spp->id = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (bsp->id, 0)));
          }
        }
      }
    }
  }
  return (Pointer) spp;
}

static void PointEditorMessage (DialoG d, Int2 mssg)

{
  PointPagePtr  ppp;

  ppp = (PointPagePtr) GetObjectExtra (d);
  if (ppp != NULL) {
    if (mssg == VIB_MSG_INIT) {
      SeqPntPtrToPointPage (d, NULL);
    } else if (mssg == VIB_MSG_ENTER) {
      Select (ppp->point);
    } else if (mssg == VIB_MSG_RESET) {
    }
  }
}

static void CleanupPointPage (GraphiC g, VoidPtr data)

{
  Int2          j;
  PointPagePtr  ppp;

  ppp = (PointPagePtr) data;
  if (ppp != NULL) {
    MemFree (ppp->bsptr);
    if (ppp->alist != NULL) {
      for (j = 0; j <= ppp->count + 1; j++) {
        MemFree (ppp->alist [j].name);
      }
    }
    MemFree (ppp->alist);
  }
  MemFree (data);
}

static DialoG CreatePointEditorDialog (GrouP h, CharPtr title, SeqEntryPtr sep,
                                       Boolean nucsOK, Boolean protsOK)

{
  BioseqPtr     bsp;
  Int4          count;
  GrouP         f;
  Int2          j;
  GrouP         m;
  GrouP         p;
  PointPagePtr  ppp;
  CharPtr       ptr;
  GrouP         s;
  Boolean       showIdTags;
  SeqIdPtr      sip;
  Char          str [128];

  p = HiddenGroup (h, 1, 0, NULL);
  SetGroupSpacing (p, 10, 10);

  ppp = (PointPagePtr) MemNew (sizeof (PointPage));
  if (ppp != NULL) {

    SetObjectExtra (p, ppp, CleanupPointPage);
    ppp->dialog = (DialoG) p;
    ppp->todialog = SeqPntPtrToPointPage;
    ppp->fromdialog = PointPageToSeqPntPtr;
    ppp->dialogmessage = PointEditorMessage;
    ppp->testdialog = NULL;
    ppp->importdialog = NULL;
    ppp->exportdialog = NULL;

    if (title != NULL && title [0] != '\0') {
      s = NormalGroup (p, 0, -2, title, systemFont, NULL);
    } else {
      s = HiddenGroup (p, 0, -2, NULL);
    }
    m = HiddenGroup (s, -1, 0, NULL);
    /*
    SetGroupSpacing (m, 10, 10);
    */

    ppp->nucsOK = nucsOK;
    ppp->protsOK = protsOK;
    ppp->showIdTags = FALSE;
    ppp->count = 0;

    if (sep != NULL) {
      count = SeqEntryCount (sep);
      count += 4;
      ppp->bsptr = MemNew (sizeof (BioseqPtr) * (size_t) count);
      ppp->alist = MemNew (sizeof (EnumFieldAssoc) * (size_t) count);
      ppp->count = 0;

      if (ppp->bsptr != NULL && ppp->alist != NULL) {
        SeqEntryExplore (sep, (Pointer) ppp, FillInPointProducts);
        j = 0;
        ppp->alist [j].name = StringSave ("     ");
        ppp->alist [j].value = (UIEnum) 0;
        for (j = 1; j <= ppp->count; j++) {
          sep = ppp->bsptr [j];
          if (sep != NULL && sep->choice == 1 && sep->data.ptrvalue != NULL) {
            bsp = (BioseqPtr) sep->data.ptrvalue;
            sip = SeqIdFindWorst (bsp->id);
            SeqIdWrite (sip, str, PRINTID_REPORT, sizeof (str));
            ptr = StringChr (str, '|');
            showIdTags = FALSE;
            if (ptr == NULL) {
              ptr = str;
            } else if (showIdTags) {
              ptr = str;
            } else {
              ptr++;
            }
            ppp->alist [j].name = StringSave (ptr);
            ppp->alist [j].value = (UIEnum) j;
          }
        }
        j = ppp->count + 1;
        ppp->alist [j].name = NULL;
        ppp->alist [j].value = (UIEnum) 0;
      }

    } else {
      ppp->alist = MemNew (sizeof (EnumFieldAssoc) * (size_t) 4);
      if (ppp->alist != NULL) {
        j = 0;
        ppp->alist [j].name = StringSave ("     ");
        ppp->alist [j].value = (UIEnum) 0;
        j = 1;
        ppp->alist [j].name = NULL;
        ppp->alist [j].value = (UIEnum) 0;

      }
    }

    f = HiddenGroup (m, 6, 0, NULL);
    /*StaticPrompt (f, "Point", 0, dialogTextHeight, programFont, 'l');*/
    ppp->point = DialogText (f, "", 5, NULL);
    if (nucsOK) {
      /*StaticPrompt (f, "Strand", 0, popupMenuHeight, programFont, 'c');*/
      ppp->strand = PopupList (f, TRUE, NULL);
      InitEnumPopup (ppp->strand, strand_alist, NULL);
    }
    /*StaticPrompt (f, "SeqID", 0, popupMenuHeight, programFont, 'l');*/
    ppp->seqIdx = PopupList (f, TRUE, NULL);
    InitEnumPopup (ppp->seqIdx, ppp->alist, NULL);
  }

  return (DialoG) p;
}

static void SeqLocPtrToBondPage (DialoG d, Pointer data)


{
  BondPagePtr  bpp;
  SeqBondPtr   sbp;
  SeqIdPtr     sip;
  SeqLocPtr    slp;
  SeqPnt       sqp;

  bpp = (BondPagePtr) GetObjectExtra (d);
  if (bpp == NULL) return;
  slp = (SeqLocPtr) data;
  if (slp != NULL) {
    if (slp->choice == SEQLOC_BOND) {
      sbp = (SeqBondPtr) slp->data.ptrvalue;
      if (sbp != NULL) {
        PointerToDialog (bpp->pointA, (Pointer) sbp->a);
        PointerToDialog (bpp->pointB, (Pointer) sbp->b);
      }
    } else {
      sip = SeqLocId (slp);
      if (sip != NULL) {
        sqp.strand = SeqLocStrand (slp);
        sqp.id = sip;
        sqp.point = SeqLocStart (slp);
        PointerToDialog (bpp->pointA, (Pointer) &sqp);
        sqp.point = SeqLocStop (slp);
        PointerToDialog (bpp->pointB, (Pointer) &sqp);
      }
    }
  }
}

static Pointer BondPageToSeqLocPtr (DialoG d)

{
  BondPagePtr  bpp;
  SeqBondPtr   sbp;
  SeqLocPtr    slp;

  bpp = (BondPagePtr) GetObjectExtra (d);
  if (bpp == NULL) return NULL;
  slp = NULL;
  sbp = SeqBondNew ();
  if (sbp != NULL) {
    slp = ValNodeNew (NULL);
    if (slp != NULL) {
      slp->choice = SEQLOC_BOND;
      slp->data.ptrvalue = (Pointer) sbp;
      sbp->a = DialogToPointer (bpp->pointA);
      sbp->b = DialogToPointer (bpp->pointB);
    } else {
      SeqBondFree (sbp);
    }
  }
  return (Pointer) slp;
}

static void BondEditorMessage (DialoG d, Int2 mssg)

{
  BondPagePtr  bpp;

  bpp = (BondPagePtr) GetObjectExtra (d);
  if (bpp != NULL) {
    if (mssg == VIB_MSG_INIT) {
      SeqLocPtrToBondPage (d, NULL);
    } else if (mssg == VIB_MSG_ENTER) {
      SendMessageToDialog (bpp->pointA, VIB_MSG_ENTER);
    } else if (mssg == VIB_MSG_RESET) {
    }
  }
}

extern DialoG CreateBondEditorDialog (GrouP h, CharPtr title, SeqEntryPtr sep);

extern DialoG CreateBondEditorDialog (GrouP h, CharPtr title, SeqEntryPtr sep)

{
  BondPagePtr  bpp;
  GrouP        f;
  GrouP        m;
  GrouP        p;
  GrouP        s;

  p = HiddenGroup (h, 1, 0, NULL);
  SetGroupSpacing (p, 10, 10);

  bpp = (BondPagePtr) MemNew (sizeof (BondPage));
  if (bpp != NULL) {

    SetObjectExtra (p, bpp, StdCleanupExtraProc);
    bpp->dialog = (DialoG) p;
    bpp->todialog = SeqLocPtrToBondPage;
    bpp->fromdialog = BondPageToSeqLocPtr;
    bpp->dialogmessage = BondEditorMessage;
    bpp->testdialog = NULL;
    bpp->importdialog = NULL;
    bpp->exportdialog = NULL;

    if (title != NULL && title [0] != '\0') {
      s = NormalGroup (p, 0, -2, title, systemFont, NULL);
    } else {
      s = HiddenGroup (p, 0, -2, NULL);
    }
    m = HiddenGroup (s, -1, 0, NULL);
    /*
    SetGroupSpacing (m, 10, 10);
    */

    f = HiddenGroup (m, 2, 0, NULL);
    StaticPrompt (f, "From", 0, popupMenuHeight, programFont, 'l');
    bpp->pointA = CreatePointEditorDialog (f, NULL, sep, FALSE, TRUE);
    StaticPrompt (f, "(To)", 0, popupMenuHeight, programFont, 'l');
    bpp->pointB = CreatePointEditorDialog (f, NULL, sep, FALSE, TRUE);
  }

  return (DialoG) p;
}

void GetRidOfEmptyFeatsDescStrings (Uint2 entityID, SeqEntryPtr sep)

{
  if (entityID < 1 && sep == NULL) return;
  if (entityID > 0 && sep == NULL) {
    sep = GetTopSeqEntryForEntityID (entityID);
  }
  if (sep == NULL) return;
  SeqEntryExplore (sep, NULL, GetRidOfEmptyFeatsDescCallback);
}

extern Int2 LIBCALLBACK StdVibrantEditorMsgFunc (OMMsgStructPtr ommsp)

{
  BaseFormPtr    bfp;
  OMUserDataPtr  omudp;

  omudp = (OMUserDataPtr)(ommsp->omuserdata);
  if (omudp == NULL) return OM_MSG_RET_ERROR;
  bfp = (BaseFormPtr) omudp->userdata.ptrvalue;
  if (bfp == NULL) return OM_MSG_RET_ERROR;
  switch (ommsp->message) {
    case OM_MSG_DEL:
      Remove (bfp->form);
      Update ();
      break;
    default :
      break;
  }
  return OM_MSG_RET_OK;
}

/* launch url section */

NLM_EXTERN void LaunchEntrezURL (CharPtr database, Int4 uid, CharPtr format)

{
#ifdef WIN_MOTIF
  NS_Window  window = NULL;
#endif

  Char  url [256];

  if (uid < 1 || StringHasNoText (database) || StringHasNoText (format)) return;
  sprintf (url,
           "http://www.ncbi.nlm.nih.gov:80/entrez/query.fcgi?cmd=Retrieve&db=%s&list_uids=%ld&dopt=%s",
            database, (long) uid, format);

#ifdef WIN_MAC
  Nlm_SendURLAppleEvent (url, "MOSS", NULL);
#endif
#ifdef WIN_MSWIN
  if (! Nlm_MSWin_OpenDocument (url)) {
    Message (MSG_POST, "Unable to launch browser");
  }
#endif
#ifdef WIN_MOTIF
  if (! NS_OpenURL (&window, url, NULL, TRUE)) {
    Message (MSG_POST, "Unable to launch browser");
  }
  NS_WindowFree (window);
#endif
}

extern void ModalAcceptButton (ButtoN b)
{
  ModalAcceptCancelPtr acp;
  
  acp = (ModalAcceptCancelPtr) GetObjectExtra (b);
  if (acp != NULL)
  {
    acp->accepted = TRUE;
  }
}

extern void ModalCancelButton (ButtoN b)
{
  ModalAcceptCancelPtr acp;
  
  acp = (ModalAcceptCancelPtr) GetObjectExtra (b);
  if (acp != NULL)
  {
    acp->cancelled = TRUE;
  }
}

extern void ModalThirdOptionButton (ButtoN b)
{
  ModalAcceptCancelPtr acp;
  
  acp = (ModalAcceptCancelPtr) GetObjectExtra (b);
  if (acp != NULL)
  {
    acp->third_option = TRUE;
  }
}


NLM_EXTERN Int4 
ThreeOptionsDlg
(CharPtr title_txt,
 CharPtr explain_txt,
 CharPtr opt1,
 CharPtr opt2,
 CharPtr opt3)
{
  WindoW w;
  GrouP  h, c;
  ButtoN b;
  GrouP  p;
  ModalAcceptCancelData acd;
  
  
  w = MovableModalWindow (-20, -13, -10, -10, title_txt, NULL);
  h = HiddenGroup(w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  p = MultiLinePrompt (h, explain_txt, 300, programFont);
    
  c = HiddenGroup (h, 3, 0, NULL);
  b = PushButton (c, opt1, ModalAcceptButton);
  SetObjectExtra (b, &acd, NULL);
  b = PushButton (c, opt2, ModalCancelButton);
  SetObjectExtra (b, &acd, NULL);
  
  if (!StringHasNoText (opt3)) {
    b = PushButton (c, opt3, ModalThirdOptionButton);
    SetObjectExtra (b, &acd, NULL);
  }
  
  AlignObjects (ALIGN_CENTER, (HANDLE) p,
                              (HANDLE) c, 
                              NULL);

  Show (w);
  Select (w);
  
  acd.cancelled = FALSE;
  acd.third_option = FALSE;
  acd.accepted = FALSE;
  while (!acd.accepted && ! acd.cancelled && !acd.third_option)
  {
    ProcessExternalEvent ();
    Update ();
  }
  ProcessAnEvent ();
  Remove (w);
  
  if (acd.accepted)
  {
    return 1;
  }
  else if (acd.cancelled)
  {
    return 2;
  }
  else
  {
    return 3;
  }
}


typedef struct tabledisplay 
{
  DIALOG_MESSAGE_BLOCK
  PaneL panel;
  ValNodePtr row_list;
  Int4 frozen_header;
  Int4 frozen_left;
  Int4 table_inset;
  Int4 char_width;
  Int4 descent;
  FonT display_font;
  TableDisplayDblClick dbl_click;
  Pointer dbl_click_data;
  TableDisplayLeftInRed left_in_red;
  Pointer left_in_red_data;
  Int4 select_x;
  Int4 select_y;
  Int4 select_cell_offset;
  Boolean editable;
} TableDisplayData, PNTR TableDisplayPtr;

extern ValNodePtr FreeTableDisplayRowList (ValNodePtr row_list)
{
  ValNodePtr row_vnp, column_list;
  
  if (row_list != NULL)
  {
    /* free table text */
    for (row_vnp = row_list; row_vnp != NULL; row_vnp = row_vnp->next)
    {
      column_list = (ValNodePtr) row_vnp->data.ptrvalue;
      row_vnp->data.ptrvalue = ValNodeFreeData (column_list);
    }
    row_list = ValNodeFree (row_list);
  }
  return row_list;
}

extern void PrintTableDisplayRowListToFile (ValNodePtr row_list, FILE *fp)
{
  ValNodePtr row_vnp, col_vnp, column_list;
  CharPtr    txt_val;
  
  if (row_list == NULL || fp == NULL)
  {
    return;
  }
  
  for (row_vnp = row_list; row_vnp != NULL; row_vnp = row_vnp->next)
  {
    column_list = (ValNodePtr) row_vnp->data.ptrvalue;
    for (col_vnp = column_list; col_vnp != NULL; col_vnp = col_vnp->next)
    {
      txt_val = (CharPtr) col_vnp->data.ptrvalue;
      if (!StringHasNoText (txt_val))
      {
        fprintf (fp, "%s", txt_val);
      }
      if (col_vnp->next == NULL)
      {
        fprintf (fp, "\n");
      }
      else
      {
        fprintf (fp, "\t");
      }
    }
  }
}

static ValNodePtr ValNodeStringListCopy (ValNodePtr orig_list)
{
  ValNodePtr new_list = NULL;
  
  if (orig_list == NULL)
  {
    return NULL;
  }
  
  new_list = ValNodeNew (NULL);
  new_list->choice = orig_list->choice;
  new_list->data.ptrvalue = StringSave (orig_list->data.ptrvalue);
  new_list->next = ValNodeStringListCopy (orig_list->next);
  return new_list;
}

extern ValNodePtr CopyTableDisplayRowList (ValNodePtr row_list)
{
  ValNodePtr new_row_list = NULL;
  
  if (row_list == NULL)
  {
    return NULL; 
  }
  
  new_row_list = ValNodeNew (NULL);
  new_row_list->choice = row_list->choice;
  new_row_list->data.ptrvalue = ValNodeStringListCopy (row_list->data.ptrvalue);
  new_row_list->next = CopyTableDisplayRowList (row_list->next);
  return new_row_list;
}

static void CleanupTableDisplayDialog (GraphiC g, VoidPtr data)
{
  TableDisplayPtr dlg;

  dlg = (TableDisplayPtr) data;
  if (dlg != NULL) {
    dlg->row_list = FreeTableDisplayRowList (dlg->row_list);
  }
  StdCleanupExtraProc (g, data);
}

static void UpdateTableDisplayDialogScrollBars (TableDisplayPtr dlg)
{
  BaR  sb_vert;
  BaR  sb_horiz;
  Int4 start_row, start_col;
  Int4 num_rows, num_columns, visible_rows;
  Int4 new_vmax, new_hmax, old_vmax, old_hmax;
  RecT r;
  Int4 x, y;
  
  if (dlg == NULL)
  {
    return;
  }
  
  sb_vert  = GetSlateVScrollBar ((SlatE) dlg->panel);
  sb_horiz = GetSlateHScrollBar ((SlatE) dlg->panel);
  
  start_row = GetBarValue (sb_vert) + dlg->frozen_header;
  start_col = GetBarValue (sb_horiz) + dlg->frozen_left;
    
  if (dlg->row_list == NULL)
  {
    num_rows = 0;
    num_columns = 0;
  }
  else
  {
    num_rows = ValNodeLen (dlg->row_list);
    num_columns = ValNodeLen (dlg->row_list->data.ptrvalue);
  }

  ObjectRect (dlg->panel, &r);
  InsetRect (&r, dlg->table_inset, dlg->table_inset);
  x = r.left + 1;
  y = r.top + stdLineHeight;
    
  visible_rows = (r.bottom - r.top - 2 * dlg->table_inset) / stdLineHeight - dlg->frozen_header;
  new_vmax = num_rows - visible_rows - 1;
  new_hmax = num_columns - dlg->frozen_left - 1;
  if (new_vmax < 0)
  {
    new_vmax = 0;
  }
  if (new_hmax < 0)
  {
    new_hmax = 0;
  }
  old_vmax = GetBarMax (sb_vert);
  old_hmax = GetBarMax (sb_horiz);
  
  if (old_vmax != new_vmax)
  {
    CorrectBarMax (sb_vert, new_vmax);
    if (start_row > new_vmax + dlg->frozen_header)
    {
      start_row = new_vmax + dlg->frozen_header;
    }
    CorrectBarValue (sb_vert, start_row - dlg->frozen_header);
    CorrectBarPage (sb_vert, 1, 1);
  }
  
  if (old_hmax != new_hmax)
  {
    CorrectBarMax (sb_horiz, new_hmax);
    if (start_col > new_hmax + dlg->frozen_left)
    {
      start_col = new_hmax + dlg->frozen_left;
    }
    CorrectBarValue (sb_horiz, start_col - dlg->frozen_left);
    CorrectBarPage (sb_horiz, 1, 1);
  }  
}

static void RowsToTableDisplayDialog (DialoG d, Pointer userdata)
{
  TableDisplayPtr dlg;
  RecT            r;
    WindoW          temport;

  dlg = (TableDisplayPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  dlg->row_list = FreeTableDisplayRowList (dlg->row_list);
  dlg->row_list = CopyTableDisplayRowList (userdata);
  UpdateTableDisplayDialogScrollBars (dlg);
  temport = SavePort (dlg->panel);
  Select (dlg->panel);
  ObjectRect (dlg->panel, &r);
  InvalRect (&r);  
  Update ();
  RestorePort (temport);
}

static Pointer TableDisplayDialogToRows (DialoG d)
{
  TableDisplayPtr dlg;

  dlg = (TableDisplayPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  return CopyTableDisplayRowList (dlg->row_list);
}

static Int4 
PrepareTableDisplayTextBuffer 
(CharPtr buf,
 Int4    remaining_chars_in_row,
 Int4    col_width,
 CharPtr data)
{
  Uint4 chars_to_paint = 0;
  if (buf == NULL)
  {
    return 0;
  }
  
  if (remaining_chars_in_row < col_width)
  {
    chars_to_paint = remaining_chars_in_row;
    StringNCpy (buf, data, chars_to_paint);
    buf [chars_to_paint] = 0;
  }
  else
  {
    chars_to_paint = col_width;
    StringNCpy (buf, data, chars_to_paint);
    buf [chars_to_paint] = 0;
    if (StringLen (data) > chars_to_paint && chars_to_paint > 2)
    {
      buf [chars_to_paint - 1] = '.';
      buf [chars_to_paint - 2] = '.';
      buf [chars_to_paint - 3] = '.';
    }
  }
  return chars_to_paint;
}

static void DrawTableDisplayLine (Int4 x, Int4 y, 
 ValNodePtr header_row,
 ValNodePtr data_row,
 CharPtr    buf,
 Int4       row_length,
 Int4       frozen_left,
 Int4       start_col,
 Int4       char_width,
 Int4       descent,
 Boolean    left_in_red)
{
  ValNodePtr header_vnp, data_vnp;
  Int4       x_offset, chars_to_paint, col_num;
  PoinT      pt1, pt2;
  RecT       rct;
  
  /* draw left margin */
  
  for (header_vnp = header_row, data_vnp = data_row, x_offset = 0, col_num = 0;
       header_vnp != NULL && data_vnp != NULL && x_offset < row_length && col_num < frozen_left;
       header_vnp = header_vnp->next, data_vnp = data_vnp->next, col_num++)
  {
    Gray ();
    InvertColors ();
    if (left_in_red)
    {
      Red ();
    }
    else
    {
      White ();
    }
    chars_to_paint = PrepareTableDisplayTextBuffer (buf, 
                                   (row_length - x_offset) / char_width,
                                   header_vnp->choice,
                                   data_vnp->data.ptrvalue);
        
    LoadRect (&rct, x + x_offset, y + descent,
              x + x_offset + (chars_to_paint + 2) * char_width, 
              y - stdLineHeight + descent);
    EraseRect (&rct);

    PaintStringEx ( (CharPtr)buf, x + x_offset, y);
    x_offset += (chars_to_paint + 2) * char_width;
    InvertColors ();
    Black ();
  }
  
  if (frozen_left > 0)
  {
    pt1.x = x + x_offset - 1;
    pt1.y = y;
    pt2.x = x + x_offset - 1;
    pt2.y = y - stdLineHeight;
    DrawLine (pt1, pt2);
  }

  
  while (col_num < start_col && header_vnp != NULL && data_vnp != NULL)
  {
    col_num++;
    header_vnp = header_vnp->next;
    data_vnp = data_vnp->next;
  }
  
  /* draw unfrozen columns */
  while (header_vnp != NULL && data_vnp != NULL && x_offset < row_length)
  {
    chars_to_paint = MIN (header_vnp->choice, (row_length - x_offset)/char_width);
    StringNCpy (buf, data_vnp->data.ptrvalue, chars_to_paint);
    buf [chars_to_paint] = 0;
    chars_to_paint = PrepareTableDisplayTextBuffer (buf, 
                                   (row_length - x_offset) / char_width,
                                   header_vnp->choice,
                                   data_vnp->data.ptrvalue);

    PaintStringEx ( (CharPtr)buf, x + x_offset, y);
    x_offset += (chars_to_paint + 2) * char_width;
    header_vnp = header_vnp->next;
    data_vnp = data_vnp->next;
  }
}


static void DrawTableDisplayCaret (Int4 x, Int4 y,  
 ValNodePtr header_row,
 ValNodePtr data_row,
 CharPtr    buf,
 Int4       row_length,
 Int4       frozen_left,
 Int4       start_col,
 Int4       char_width,
 Int4       select_x,
 Int4       select_cell_offset)
{

  ValNodePtr header_vnp, data_vnp;
  Int4       x_offset, chars_to_paint, col_num;
  PoinT      pt1, pt2;
  Boolean    found = FALSE;
  
  /* draw left margin */
  
  for (header_vnp = header_row, data_vnp = data_row, x_offset = 0, col_num = 0;
       header_vnp != NULL && data_vnp != NULL && x_offset < row_length && col_num < frozen_left && !found;
       header_vnp = header_vnp->next, data_vnp = data_vnp->next, col_num++)
  {
    /* no editing in frozen left, so no caret */
    chars_to_paint = PrepareTableDisplayTextBuffer (buf, 
                                   (row_length - x_offset) / char_width,
                                   header_vnp->choice,
                                   data_vnp->data.ptrvalue);
    x_offset += (chars_to_paint + 2) * char_width;
  }
    
  while (col_num < start_col && header_vnp != NULL && data_vnp != NULL)
  {
    col_num++;
    header_vnp = header_vnp->next;
    data_vnp = data_vnp->next;
  }
  
  /* draw unfrozen columns */
  while (header_vnp != NULL && data_vnp != NULL && x_offset < row_length && !found)
  {
    if ((col_num - start_col) + frozen_left == select_x) {
      Black();
      pt1.x = x + x_offset + (select_cell_offset * char_width) - 1;
      pt1.y = y;
      pt2.x = x + x_offset + (select_cell_offset * char_width);
      pt2.y = y - 1;
      DrawLine (pt1, pt2);
      pt1.x = x + x_offset + (select_cell_offset * char_width) + 1;
      DrawLine (pt1, pt2);
      found = TRUE;
    }

    chars_to_paint = MIN (header_vnp->choice, (row_length - x_offset)/char_width);
    x_offset += (chars_to_paint + 2) * char_width;
    header_vnp = header_vnp->next;
    data_vnp = data_vnp->next;
    col_num++;
  }
}


static void OnDrawTableDisplay (PaneL p)
{
  TableDisplayPtr dlg;
  BaR             sb_vert, sb_horiz;
  Int4            start_row, start_col;
  RecT            r;
  Int4            x, y, row, row_length;
  CharPtr         row_buffer;
  ValNodePtr      row_vnp;
  PoinT           pt1, pt2;
  Boolean         left_in_red;

  dlg = (TableDisplayPtr) GetObjectExtra (p);
  if (dlg == NULL)
  {
    return;
  }
  
  sb_vert  = GetSlateVScrollBar ((SlatE) p);
  sb_horiz = GetSlateHScrollBar ((SlatE) p);
  
  start_row = GetBarValue (sb_vert) + dlg->frozen_header;
  start_col = GetBarValue (sb_horiz) + dlg->frozen_left;

  ObjectRect (p, &r);
  InsetRect (&r, dlg->table_inset, dlg->table_inset);
  x = r.left + 1;
  y = r.top + stdLineHeight;

  SelectFont (programFont); 
  
  row_length = r.right - r.left - 2;
  row_buffer = (CharPtr) MemNew (((row_length / dlg->char_width) + 1) * sizeof (Char));
  
  for (row = 0, row_vnp = dlg->row_list;
       row < dlg->frozen_header && y <= r.bottom - 2 * dlg->table_inset && row_vnp != NULL;
       row++, row_vnp = row_vnp->next)
  {
    DrawTableDisplayLine (x, y, dlg->row_list->data.ptrvalue, row_vnp->data.ptrvalue,
                          row_buffer, row_length, dlg->frozen_left, start_col, 
                          dlg->char_width, dlg->descent, FALSE);
    y += stdLineHeight;
  }
  
  while (row < start_row && row_vnp != NULL)
  {
    row++;
    row_vnp = row_vnp->next;
  }
  
  while (row_vnp != NULL && y <= r.bottom - 2 * dlg->table_inset)
  {
    left_in_red = FALSE;
    if (dlg->left_in_red != NULL)
    {
      left_in_red = (dlg->left_in_red) (row, dlg->row_list, dlg->left_in_red_data);
    }
    DrawTableDisplayLine (x, y, dlg->row_list->data.ptrvalue, row_vnp->data.ptrvalue,
                          row_buffer, row_length, dlg->frozen_left, start_col,
                          dlg->char_width, dlg->descent, left_in_red);
    row_vnp = row_vnp->next;
    if (row == dlg->select_y) {
      DrawTableDisplayCaret (x, y, dlg->row_list->data.ptrvalue,
                          row_vnp == NULL ? NULL : row_vnp->data.ptrvalue,
                          row_buffer, row_length, dlg->frozen_left, start_col, 
                          dlg->char_width, dlg->select_x, dlg->select_cell_offset);
    }
    y += stdLineHeight;
    row++;
  }
  
  /* draw line to separate header from remaining lines */
  if (dlg->frozen_header > 0)
  {
    Black ();
    pt1.x = x;
    pt1.y = r.top + stdLineHeight + dlg->descent;
    pt2.x = x + row_length;
    pt2.y = r.top + stdLineHeight + dlg->descent;
    DrawLine (pt1, pt2);
  }
  

}

static void OnVScrollTableDisplay (BaR sb, SlatE s, Int4 newval, Int4 oldval)
{
  RecT   r;
  WindoW temport;

  temport = SavePort (s);
  Select (s);
  ObjectRect (s, &r);
  InvalRect (&r);  
  RestorePort (temport);
  Update ();
}

static void OnHScrollTableDisplay (BaR sb, SlatE s, Int4 newval, Int4 oldval)
{
  RecT   r;
  WindoW temport;

  temport = SavePort (s);
  Select (s);
  ObjectRect (s, &r);
  InvalRect (&r);  
  RestorePort (temport);
  Update ();
}

static CharPtr TableDisplayGetTextForCell (TableDisplayPtr dlg, PoinT pt);

static PoinT GetTableDisplayCell (TableDisplayPtr dlg, PoinT pt, Int4Ptr char_offset)
{
  BaR sb_horiz;
  BaR sb_vert;
  Int4 start_row, start_col;
  RecT r;
  PoinT cell_coord;
  Int4  x, y;
  ValNodePtr header_vnp;
  Int4  col_width;
  CharPtr cell_text;
  Int4    chars_skipped, cell_len;
  Uint1 chs;
  
  cell_coord.x = 0;
  cell_coord.y = 0;
  
  if (dlg == NULL || dlg->row_list == NULL)
  {
    return cell_coord;
  }
  
  sb_vert  = GetSlateVScrollBar ((SlatE) dlg->panel);
  sb_horiz = GetSlateHScrollBar ((SlatE) dlg->panel);
  
  start_row = GetBarValue (sb_vert) + dlg->frozen_header;
  start_col = GetBarValue (sb_horiz) + dlg->frozen_left;
  
  ObjectRect (dlg->panel, &r);
  InsetRect (&r, dlg->table_inset, dlg->table_inset);
  x = pt.x - r.left;
  y = pt.y - r.top;
  
  cell_coord.y = y / stdLineHeight;
  
  if (cell_coord.y >= dlg->frozen_header)
  {
    cell_coord.y += GetBarValue (sb_vert);
  }

  header_vnp = dlg->row_list->data.ptrvalue;
  
  col_width = 0;
  while (header_vnp != NULL && col_width + (header_vnp->choice + 2) * dlg->char_width < x
         && cell_coord.x < dlg->frozen_left)
  {
    cell_coord.x++;
    col_width += (header_vnp->choice + 2) * dlg->char_width;
    header_vnp = header_vnp->next;
  }
  
  if (cell_coord.x >= dlg->frozen_left)
  {
    /* skip over unfrozen columns not currently displayed */
    while (header_vnp != NULL && cell_coord.x < start_col)
    {
      header_vnp = header_vnp->next;
      cell_coord.x++;
    }
  
    while (header_vnp != NULL && col_width + (header_vnp->choice + 2) * dlg->char_width < x)
    {
      cell_coord.x++;
      col_width += (header_vnp->choice + 2) * dlg->char_width;
      header_vnp = header_vnp->next;
    }
    if (char_offset != NULL) {
      chs = 0;
      if (header_vnp != NULL) {
        chs = header_vnp->choice;
      }
      chars_skipped = ((col_width + (chs + 2) * dlg->char_width) - x) / dlg->char_width;
      cell_text = TableDisplayGetTextForCell (dlg, cell_coord);
      cell_len = StringLen (cell_text);
      cell_text = MemFree (cell_text);
      if (chars_skipped > cell_len) {
        chars_skipped = cell_len;
      }
      *char_offset = chars_skipped;
    }
  }
  return cell_coord;
}


static ValNodePtr GetRowListCell (ValNodePtr row_list, Int4 row, Int4 column)
{
  ValNodePtr row_vnp, col_vnp;
  Int4       row_num, col_num;
  
  if (row_list == NULL || row < 0 || column < 0)
  {
    return NULL;
  }
  
  for (row_vnp = row_list, row_num = 0;
       row_vnp != NULL && row_num < row;
       row_vnp = row_vnp->next, row_num++)
  {
  }
  if (row_num != row || row_vnp == NULL)
  {
    return NULL;
  }
  for (col_vnp = row_vnp->data.ptrvalue, col_num = 0;
       col_vnp != NULL && col_num < column;
       col_vnp = col_vnp->next, col_num++)
  {
  }
  if (col_num != column || col_vnp == NULL)
  {
    return NULL;
  }
  else
  {
    return col_vnp;
  }
}


extern CharPtr GetRowListCellText (ValNodePtr row_list, Int4 row, Int4 column)
{
  ValNodePtr col_vnp;

  col_vnp = GetRowListCell (row_list, row, column);
  if (col_vnp != NULL)
  {
    return StringSave (col_vnp->data.ptrvalue);
  } 
  else
  {
    return NULL;
  }
}

static CharPtr TableDisplayGetTextForCell (TableDisplayPtr dlg, PoinT pt)
{
  if (dlg == NULL || dlg->row_list == NULL || pt.x < 0 || pt.y < 0)
  {
    return NULL;
  }
  
  return GetRowListCellText (dlg->row_list, pt.y, pt.x);
}

static void TableDisplayOnClick (PaneL p, PoinT pt)
{
  TableDisplayPtr dlg;
  Boolean         dbl_click;
  PoinT           cell_coord;
  PoinT           header_coord;
  CharPtr         cell_text;
  CharPtr         header_text;
  
  dlg = (TableDisplayPtr) GetObjectExtra (p);
  if (dlg == NULL)
  {
    return;
  }

  cell_coord = GetTableDisplayCell (dlg, pt, &(dlg->select_cell_offset));
  if (dlg->editable) {
    dlg->select_x = cell_coord.x;
    dlg->select_y = cell_coord.y;
    if (dlg->select_x == 0) {
      dlg->select_x = cell_coord.x;
    }
  }

  dbl_click = dblClick;

  if (dbl_click && dlg->dbl_click != NULL)
  {
    cell_text = TableDisplayGetTextForCell (dlg, cell_coord);
    header_coord.x = cell_coord.x;
    header_coord.y = 0;
    header_text = TableDisplayGetTextForCell (dlg, header_coord);
    (dlg->dbl_click) (cell_coord, header_text, cell_text, dlg->dbl_click_data);
    MemFree (cell_text);
    MemFree (header_text);
  } else if (dlg->editable) {
    inval_panel (p, -1, -1);
  }
}

extern FonT GetTableDisplayDefaultFont (void)
{
  FonT display_font = NULL;
  
#ifdef WIN_MAC
  display_font = ParseFont ("Monaco, 9");
#endif
#ifdef WIN_MSWIN
  display_font = ParseFont ("Courier, 9");
#endif
#ifdef WIN_MOTIF
  display_font = ParseFont ("fixed, 12");
#endif  
  return display_font;
}

extern DialoG TableDisplayDialog (GrouP parent, Int4 width, Int4 height,
                                  Int4 frozen_header, Int4 frozen_left,
                                  TableDisplayDblClick dbl_click,
                                  Pointer dbl_click_data,
                                  TableDisplayLeftInRed left_in_red,
                                  Pointer left_in_red_data)
{
  TableDisplayPtr dlg;
  GrouP           p;
  
  dlg = (TableDisplayPtr) MemNew (sizeof (TableDisplayData));
  if (dlg == NULL)
  {
    return NULL;
  }
  p = HiddenGroup (parent, -1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupTableDisplayDialog);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = RowsToTableDisplayDialog;
  dlg->fromdialog = TableDisplayDialogToRows;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;
  
  dlg->row_list = NULL;
  dlg->frozen_header = frozen_header;
  dlg->frozen_left = frozen_left;
  dlg->table_inset = 4;
  dlg->dbl_click = dbl_click;
  dlg->dbl_click_data = dbl_click_data;
  dlg->left_in_red = left_in_red;
  dlg->left_in_red_data = left_in_red_data;
  
  dlg->display_font = GetTableDisplayDefaultFont ();

  SelectFont (dlg->display_font);
  dlg->char_width  = CharWidth ('0');
  dlg->descent = Descent ();
  
  dlg->panel = AutonomousPanel4 (p, width, height, OnDrawTableDisplay,
                               OnVScrollTableDisplay, OnHScrollTableDisplay,
                               sizeof (TableDisplayData), NULL, NULL); 
  SetObjectExtra (dlg->panel, dlg, NULL);
  SetPanelClick(dlg->panel, TableDisplayOnClick, NULL, NULL, NULL);
  
  return (DialoG) p;  
}


static void EdTableOnKey (SlatE s, Char ch)
{
  PaneL            pnl;
  TableDisplayPtr  dlg;
  ValNodePtr       col_vnp;
  CharPtr          cell_text, new_cell_text;
  Int4             orig_len;

  pnl = (PaneL) s;
  Select (pnl);
  dlg = (TableDisplayPtr) GetObjectExtra (pnl);
  if (dlg == NULL) return;

  if ( (int) ch == 0 ) return;

  CaptureSlateFocus ((SlatE) dlg->panel);
  
  col_vnp = GetRowListCell (dlg->row_list, dlg->select_y, dlg->select_x);
  if (col_vnp != NULL) {
    cell_text = col_vnp->data.ptrvalue;
    orig_len = StringLen (cell_text);
    new_cell_text = (CharPtr) MemNew (sizeof (Char) * (orig_len + 2));
    new_cell_text[0] = 0;
    if (dlg->select_cell_offset > 0) {
      StringNCpy (new_cell_text, cell_text, dlg->select_cell_offset);
    }
    new_cell_text[dlg->select_cell_offset] = ch;
    if (dlg->select_cell_offset < orig_len) {
      StringCpy (new_cell_text + dlg->select_cell_offset + 1, cell_text + dlg->select_cell_offset);
    }
    new_cell_text[orig_len + 1] = 0;
    cell_text = MemFree (cell_text);
    col_vnp->data.ptrvalue = new_cell_text;
    /* increment offset to be after what we have just inserted */
    dlg->select_cell_offset++;
    /* redraw */
    inval_panel (pnl, -1, -1);
  }

#if 0
  /* later, handle control key combos */
#ifdef WIN_MSWIN
  if (ch == 3)
  {
    SeqEdCopy (sefp);
  }
  else if (ch == 24)
  {
    SeqEdCut (sefp);
  }
  else if (ch == 22)
  {
    SeqEdPaste (sefp);
  }
#else
  if (ctrlKey)
  {
    if (ch == 'c')
    {
      SeqEdCopy (sefp);	
    }
    else if (ch == 'x')
    {
      SeqEdCut (sefp);
    }
    else if (ch == 'v')
    {
      SeqEdPaste (sefp);
    }
  	return;
  }
#endif
  else
  {
    /* look at key pressed - if it's a good sequence character, insert it */
    if ( (str = char_to_insert (&ch, 1, sefp->bfp->bvd.bsp->mol)) != NULL) 
    {
      if (sefp->edit_pos_start != sefp->edit_pos_end)
      {
        SeqEdDelete (sefp, FALSE);
      }
      if (AddJournalEntry (eSeqEdInsert, sefp->edit_pos_start,
   	                   StringLen (str), str, sefp))
      {
   	    sefp->edit_pos_start += StringLen (str);
   	    sefp->edit_pos_end += StringLen (str);
        ResizeSeqEdView (sefp);
        Select (sefp->bfp->bvd.seqView);
  	    inval_panel (pnl, -1, -1);
      }
    }
    else
    {
      /* handle movements and deletions */
      new_pos = sefp->edit_pos_start;
      switch (ch)
      {
        case NLM_LEFT: 
          new_pos--;
          break;
        case NLM_RIGHT:
    	    new_pos++;
    	    break;
    	  case NLM_UP:
    	    new_pos -= sefp->bfp->bvd.CharsAtLine;
    	    break;
    	  case NLM_DOWN:
    	    new_pos += sefp->bfp->bvd.CharsAtLine;
    	    if (new_pos > sefp->bfp->bvd.bsp->length
    	        && new_pos - sefp->bfp->bvd.bsp->length < sefp->bfp->bvd.CharsAtLine)
    	    {
            new_pos = sefp->bfp->bvd.bsp->length;
          }
    	    break;
        case NLM_DEL:
          /* handle deletion */
          if (sefp->edit_pos_end == sefp->edit_pos_start)
          {
            sefp->edit_pos_end = sefp->edit_pos_start + 1;
            if (sefp->edit_pos_end > sefp->bfp->bvd.bsp->length)
            {
              sefp->edit_pos_end = sefp->edit_pos_start;
              return;
            }
          }
          RemapSeqEdIntervalForGap (sefp);
          SeqEdDelete (sefp, FALSE);
          ResizeSeqEdView (sefp);
          Select (sefp->bfp->bvd.seqView);
	        inval_panel (pnl, -1, -1);
          break;
        case NLM_BACK:
    	    /* handle backspace */
    	    if (sefp->edit_pos_start == sefp->edit_pos_end)
    	    {
    	      if (sefp->edit_pos_start == 0)
    	      {
    	        del_start = -1;
            }
    	      else
    	      {
              del_start = sefp->edit_pos_start - 1;
              del_stop = sefp->edit_pos_start;
  	        }
          }
    	    else
    	    {
            del_start = sefp->edit_pos_start;
            del_stop = sefp->edit_pos_end;
  	      }
  	  	  
          if (del_start >= 0)
          {
            sefp->edit_pos_start = del_start;
            sefp->edit_pos_end = del_stop;
            RemapSeqEdIntervalForGap (sefp);
            new_pos = sefp->edit_pos_start;
            SeqEdDelete (sefp, FALSE);
            ResizeSeqEdView (sefp);
            Select (sefp->bfp->bvd.seqView);
            inval_panel (pnl, -1, -1);
  	      }
  	      break;
      }
      if (new_pos < 0 || new_pos > sefp->bfp->bvd.bsp->length)
      {
        Beep ();
      }
      else
      {
        sefp->edit_pos_start = new_pos;
  	    sefp->edit_pos_end = new_pos;
        sb = GetSlateVScrollBar (s);
        if (sb != NULL)
  	    {
  	      scroll_pos = SeqEdGetScrollPosForSequencePos (sefp->edit_pos_start, &(sefp->bfp->bvd));
          SetBarValue (sb, scroll_pos);
  	    }
        Select (sefp->bfp->bvd.seqView);
  	    inval_panel (pnl, -1, -1);
      }
    }
    SeqEdUpdateStatus (sefp);
  }
#endif
}


extern DialoG EditableTableDisplayDialog (GrouP parent, Int4 width, Int4 height,
                                  Int4 frozen_header, Int4 frozen_left,
                                  TableDisplayDblClick dbl_click,
                                  Pointer dbl_click_data,
                                  TableDisplayLeftInRed left_in_red,
                                  Pointer left_in_red_data)
{
  TableDisplayPtr dlg;
  GrouP           p;
  
  dlg = (TableDisplayPtr) MemNew (sizeof (TableDisplayData));
  if (dlg == NULL)
  {
    return NULL;
  }
  p = HiddenGroup (parent, -1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupTableDisplayDialog);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = RowsToTableDisplayDialog;
  dlg->fromdialog = TableDisplayDialogToRows;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;
  dlg->editable = TRUE;
  
  dlg->row_list = NULL;
  dlg->frozen_header = frozen_header;
  dlg->frozen_left = frozen_left;
  dlg->table_inset = 4;
  dlg->dbl_click = dbl_click;
  dlg->dbl_click_data = dbl_click_data;
  dlg->left_in_red = left_in_red;
  dlg->left_in_red_data = left_in_red_data;
  
  dlg->display_font = GetTableDisplayDefaultFont ();

  SelectFont (dlg->display_font);
  dlg->char_width  = CharWidth ('0');
  dlg->descent = Descent ();
  
  dlg->panel = AutonomousPanel4 (p, width, height, OnDrawTableDisplay,
                               OnVScrollTableDisplay, OnHScrollTableDisplay,
                               sizeof (TableDisplayData), NULL, NULL); 
  SetObjectExtra (dlg->panel, dlg, NULL);
  SetPanelClick(dlg->panel, TableDisplayOnClick, NULL, NULL, NULL);
  SetSlateChar ((SlatE) dlg->panel, EdTableOnKey);
  
  return (DialoG) p;  
}


typedef struct multiselectdialog
{
  DIALOG_MESSAGE_BLOCK
  DoC                  doc;
  ValNodePtr           selected_list;
  ParData              listPar;
  ColData              listCol;
  Int4                 num_choices;
  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_userdata;    
} MultiSelectDialogData, PNTR MultiSelectDialogPtr;

static void DataToMultiSelectionDialog (DialoG d, Pointer userdata)
{
  MultiSelectDialogPtr dlg;
  ValNodePtr           vnp;
  Boolean              all_selected = FALSE;
  
  dlg = (MultiSelectDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  dlg->selected_list = ValNodeFree (dlg->selected_list);
  for (vnp = (ValNodePtr) userdata; vnp != NULL && ! all_selected; vnp = vnp->next)
  {
    if (vnp->data.intvalue == 1)
    {
      all_selected = TRUE;
    }
    else
    {
      ValNodeAddInt (&(dlg->selected_list), vnp->data.intvalue, vnp->data.intvalue);
    }
  }
  if (all_selected)
  {
    dlg->selected_list = ValNodeFree (dlg->selected_list);
    ValNodeAddInt (&(dlg->selected_list), 1, 1);
  }
  InvalDocRows (dlg->doc, 0, 0, 0);
}

static Pointer MultiSelectionDialogToData (DialoG d)
{
  MultiSelectDialogPtr dlg;
  ValNodePtr           output_list = NULL, vnp;
 
  dlg = (MultiSelectDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;
  
  for (vnp = dlg->selected_list; vnp != NULL; vnp = vnp->next)
  {
    ValNodeAddInt (&output_list, vnp->choice, vnp->data.intvalue);
  }
  return output_list;
}

static void 
AddChoiceToSelection 
(Int2                 choice_num,
 Boolean              remove_other_choices,
 MultiSelectDialogPtr dlg)
{
  ValNodePtr already_sel, vnp;
  
  if (dlg == NULL || dlg->doc == NULL || choice_num < 1)
  {
    return;
  }
  
  if (choice_num == 1)
  {
    remove_other_choices = TRUE;
  }
  else 
  {
    /* if we have added a choice other than "All", remove the "All"
     * selection if it was present.
     */
    ValNodeFree (ValNodeExtractList (&(dlg->selected_list), 1));
    InvalDocRows (dlg->doc, 1, 1, 1);
  }
  
  already_sel = ValNodeExtractList (&(dlg->selected_list), choice_num);
  if (already_sel == NULL)
  {
    if (remove_other_choices)
    {
      /* delete old selections */
      for (vnp = dlg->selected_list; vnp != NULL; vnp = vnp->next)
      {
        InvalDocRows (dlg->doc, vnp->choice, 1, 1);
      }
      dlg->selected_list = ValNodeFree (dlg->selected_list);      
      ValNodeAddInt (&dlg->selected_list, choice_num, choice_num);
      InvalDocRows (dlg->doc, choice_num, 1, 1);
    }
    else
    {
      /* add new selection */
      ValNodeAddInt (&(dlg->selected_list), choice_num, choice_num);
      InvalDocRows (dlg->doc, choice_num, 1, 1);
    }
  }
  else
  {
    already_sel = ValNodeFree (already_sel);
    InvalDocRows (dlg->doc, choice_num, 1, 1); 
  }
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}

static void SelectChoice (DoC d, PoinT pt)
{
  Int2      item, row;
  Boolean   remove_other_choices;
  MultiSelectDialogPtr dlg;
  
  remove_other_choices = ! ctrlKey;
  
  dlg = (MultiSelectDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  MapDocPoint (d, pt, &item, &row, NULL, NULL);
  AddChoiceToSelection (item, remove_other_choices, dlg);
}

static Boolean ChoiceHighlight (DoC doc, Int2 item, Int2 row, Int2 col)
{
  MultiSelectDialogPtr dlg;
  ValNodePtr           vnp;
  
  dlg = (MultiSelectDialogPtr) GetObjectExtra (doc);
  if (dlg == NULL) return FALSE;
  
  for (vnp = dlg->selected_list; vnp != NULL; vnp = vnp->next)
  {
    if (vnp->data.intvalue == item)
    {
      return TRUE;
    }
  }
  return FALSE;
}

static void ChoiceOnKey (SlatE s, Char ch)
{
  MultiSelectDialogPtr dlg;
  CharPtr              str;
  Int2                 start_pos;
  Boolean              found = FALSE;
  ValNodePtr           vnp;
  
  dlg = (MultiSelectDialogPtr) GetObjectExtra (s);
  if (dlg == NULL) return;

  if ( (int) ch == 0 ) return;
  
  if (isalpha (ch))
  {
    /* find the position of the last choice we added */
    for (vnp = dlg->selected_list; vnp != NULL && vnp->next != NULL; vnp = vnp->next)
    {}
    
    if (vnp == NULL)
    {
      GetOffset (dlg->doc, NULL, &start_pos);
      /* start pos is one less than document row */
      start_pos ++;
      /* want to start at row after top row */
      start_pos ++;
    }
    else
    {
      /* want to start at row after currently selected row */
      start_pos = vnp->choice + 1;
    }
    
    while (!found && start_pos <= dlg->num_choices)
    {
      str = GetDocText (dlg->doc, start_pos, 1, 1);
      if (tolower (str [0]) == tolower (ch))
      {
        SetOffset (dlg->doc, 0, start_pos - 1);
        AddChoiceToSelection (start_pos, TRUE, dlg);
        found = TRUE;
      }
      str = MemFree (str);
      start_pos ++;
    }
    if (!found)
    {
      /* start searching at the top of the list */
      start_pos = 1;
      while (!found && start_pos <= dlg->num_choices)
      {
        str = GetDocText (dlg->doc, start_pos, 1, 1);
        if (tolower (str [0]) == tolower (ch))
        {
          SetOffset (dlg->doc, 0, start_pos - 1);
          AddChoiceToSelection (start_pos, TRUE, dlg);
          found = TRUE;
        }
        str = MemFree (str);
        start_pos ++;
      }
    }
  }
  else if (ch == NLM_DOWN)
  {
    /* down key */
    if (dlg->selected_list == NULL)
    {
      GetOffset (dlg->doc, NULL, &start_pos);
      start_pos ++;
    }
    else
    {
      start_pos = dlg->selected_list->choice;
    }
    start_pos ++;
    
    if (start_pos <= dlg->num_choices)
    {
      SetOffset (dlg->doc, 0, start_pos - 1);
      AddChoiceToSelection (start_pos, TRUE, dlg); 
      InvalDocRows (dlg->doc, 0, 0, 0);
    }
  }
  else if (ch == NLM_UP)
  {
    /* up key */
    if (dlg->selected_list == NULL)
    {
      GetOffset (dlg->doc, NULL, &start_pos);
      start_pos ++;
    }
    else
    {
      start_pos = dlg->selected_list->choice;
    }
    start_pos --;
    if (start_pos > 0)
    {
      SetOffset (dlg->doc, 0, start_pos - 1);
      AddChoiceToSelection (start_pos, TRUE, dlg); 
      InvalDocRows (dlg->doc, 0, 0, 0);
    }
  }
}

static DialoG 
MultiSelectDialog 
(GrouP      parent,
 ValNodePtr choice_list,
 Int4       list_height,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  MultiSelectDialogPtr dlg;
  GrouP                p;
  Int4                 height;
  Int4                 width;
  RecT                 r;
  ValNodePtr           vnp;
  
  if (choice_list == NULL)
  {
    return NULL;
  }
  dlg = (MultiSelectDialogPtr) MemNew (sizeof (MultiSelectDialogData));
  if (dlg == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (parent, -1, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = DataToMultiSelectionDialog;
  dlg->fromdialog = MultiSelectionDialogToData;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;  
  
  dlg->selected_list = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  
  SelectFont (systemFont);
  width = 0;
  for (vnp = choice_list; vnp != NULL; vnp = vnp->next)
  {
    width = MAX (width, StringWidth (vnp->data.ptrvalue) + 2);
  }
  
  dlg->num_choices = ValNodeLen (choice_list) + 1;
  
  height = LineHeight ();
  dlg->doc = DocumentPanel (p, width, height * list_height);
  SetObjectExtra (dlg->doc, dlg, NULL);
  
  dlg->listPar.openSpace = FALSE;
  dlg->listPar.keepWithNext = FALSE;
  dlg->listPar.keepTogether = FALSE;
  dlg->listPar.newPage = FALSE;
  dlg->listPar.tabStops = FALSE;
  dlg->listPar.minLines = 0;
  dlg->listPar.minHeight = 0;

  ObjectRect (dlg->doc, &r);
  InsetRect (&r, 4, 4);
  dlg->listCol.pixWidth = r.right - r.left;
  dlg->listCol.pixInset = 0;
  dlg->listCol.charWidth = 160;
  dlg->listCol.charInset = 0;
  dlg->listCol.font = systemFont;
  dlg->listCol.just = 'l';
  dlg->listCol.wrap = FALSE;
  dlg->listCol.bar = FALSE;
  dlg->listCol.underline = FALSE;
  dlg->listCol.left = FALSE;
  dlg->listCol.last = TRUE;  
  
  AppendText (dlg->doc, "All", &(dlg->listPar), &(dlg->listCol), programFont);
  for (vnp = choice_list; vnp != NULL; vnp = vnp->next)
  {
      AppendText (dlg->doc, vnp->data.ptrvalue, &(dlg->listPar), &(dlg->listCol), programFont);
  }
  SetDocAutoAdjust (dlg->doc, FALSE);
  SetDocProcs (dlg->doc, SelectChoice, NULL, NULL, NULL);
  SetDocShade (dlg->doc, NULL, NULL, ChoiceHighlight, NULL);
  SetSlateChar ((SlatE) dlg->doc, ChoiceOnKey);
  InvalDocument (dlg->doc);

  return (DialoG) p;
}

typedef struct selectiondialog
{
  DIALOG_MESSAGE_BLOCK
  DialoG     multi_select_dlg;
  LisT       list_ctrl;
  PopuP      popup_ctrl;
  Int4       num_choices;
  CharPtr    err_msg;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} SelectionDialogData, PNTR SelectionDialogPtr;

static void ResetSelectionDialog (SelectionDialogPtr dlg)
{  
  if (dlg != NULL)
  {
    if (dlg->multi_select_dlg != NULL)
    {
      PointerToDialog (dlg->multi_select_dlg, NULL);
    }
    else if (dlg->list_ctrl != NULL)
    {
      SetValue (dlg->list_ctrl, 0);
    }
    else if (dlg->popup_ctrl != NULL)
    {
      SetValue (dlg->popup_ctrl, 0);
    }
  } 
}

static void SelectionDialogChanged (LisT l)
{
  SelectionDialogPtr dlg;

  dlg = (SelectionDialogPtr) GetObjectExtra (l);
  if (dlg == NULL)
  {
    return;
  }
    
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  } 
}

static void SelectionDialogPopupChanged (PopuP p)
{
  SelectionDialogPtr dlg;
 
  dlg = (SelectionDialogPtr) GetObjectExtra (p);
  if (dlg == NULL)
  {
    return;
  }
    
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  } 
}

static void SelectionListToSelectionDialog (DialoG d, Pointer userdata)
{
  SelectionDialogPtr dlg;
  ValNodePtr         selected_list;

  dlg = (SelectionDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetSelectionDialog (dlg);  
  selected_list = (ValNodePtr) userdata;
  if (dlg->multi_select_dlg != NULL)
  {
    PointerToDialog (dlg->multi_select_dlg, selected_list);
  }
  else if (dlg->list_ctrl != NULL)
  {
    if (selected_list == NULL)
    {
      SetValue (dlg->list_ctrl, 0);
    }
    else
    {
      SetValue (dlg->list_ctrl, selected_list->data.intvalue);
    }
  }
  else if (dlg->popup_ctrl)
  {
    if (selected_list == NULL)
    {
      SetValue (dlg->popup_ctrl, 0);
    }
    else
    {
      SetValue (dlg->popup_ctrl, selected_list->data.intvalue);
    }
  }
}


static Pointer SelectionDialogToSelectionList (DialoG d)
{
  SelectionDialogPtr dlg;
  ValNodePtr         sel_list = NULL, vnp;
  Int4               i = 0;

  dlg = (SelectionDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  if (dlg->multi_select_dlg != NULL)
  {
    sel_list = (ValNodePtr) DialogToPointer (dlg->multi_select_dlg);
    if (sel_list != NULL && sel_list->choice == 1)
    {
      sel_list = ValNodeFree (sel_list);
      for (i = 2; i <= dlg->num_choices; i++)
      {
        ValNodeAddInt (&sel_list, 0, i - 1);
      }
    }
    else
    {
      for (vnp = sel_list; vnp != NULL; vnp = vnp->next)
      {
        vnp->choice = 0;
        vnp->data.intvalue = vnp->data.intvalue - 1;
      }
    }
  }
  else
  {
    if (dlg->list_ctrl != NULL)
    {
      i = GetValue (dlg->list_ctrl);
    }
    else if (dlg->popup_ctrl != NULL)
    {
      i = GetValue (dlg->popup_ctrl);
    }
    if (i > 0)
    {
      ValNodeAddInt (&sel_list, 0, i);
    }
  }
  return (Pointer) sel_list;
}

static void SelectionDialogMessage (DialoG d, Int2 mssg)

{
  SelectionDialogPtr dlg;
  ValNode            vn;

  dlg = (SelectionDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        ResetSelectionDialog (dlg);
        break;
      case VIB_MSG_ENTER :
        if (dlg->multi_select_dlg != NULL)
        {
          Select (dlg->multi_select_dlg);
        }
        else if (dlg->list_ctrl != NULL)
        {
          Select (dlg->list_ctrl);
        }
        else if (dlg->popup_ctrl != NULL)
        {
          Select (dlg->popup_ctrl);
        }
        break;
      case NUM_VIB_MSG + 1:
        if (dlg->multi_select_dlg != NULL)
        {
          vn.next = NULL;
          vn.choice = 1;
          vn.data.intvalue = 1;
          PointerToDialog (dlg->multi_select_dlg, &vn);
        }
        else if (dlg->list_ctrl != NULL)
        {
          SetItemStatus (dlg->list_ctrl, 1, TRUE);
        }
        else if (dlg->popup_ctrl != NULL)
        {
          SetValue (dlg->popup_ctrl, 1);
        }
        SelectionDialogChanged (dlg->list_ctrl);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestSelectionDialog (DialoG d)

{
  SelectionDialogPtr dlg;
  ValNodePtr         head = NULL, vnp;
  Boolean            any_selected = FALSE;

  dlg = (SelectionDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (dlg->multi_select_dlg != NULL)
    {
      vnp = DialogToPointer (dlg->multi_select_dlg);
      if (vnp != NULL)
      {
        any_selected = TRUE;
        vnp = ValNodeFree (vnp);
      }
    }
    else if (dlg->list_ctrl != NULL)
    {
      if (GetValue (dlg->list_ctrl) > 0)
      {
        any_selected = TRUE;
      }
    }
    else if (dlg->popup_ctrl != NULL)
    {
      if (GetValue (dlg->popup_ctrl) > 0)
      {
        any_selected = TRUE;
      }
    }
    if (!any_selected)
    {
      head = AddStringToValNodeChain (head, dlg->err_msg, 1);
    }
  }
  return head;
}

/* err_msg is the message to put in the results from TestDialog if nothing is selected */
/* choice_list should be a valnode list of strings to use for the names of the choices. */
/* All is automatically included as a choice if allow_multi is true. */
/* The ValNodeList returned is a list of integers indicating the position of the item
 * in the list - 1 is the first item, 2 is the second item, etc. */
extern DialoG SelectionDialogExEx 
(GrouP h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 CharPtr                  err_msg,
 ValNodePtr               choice_list,
 Int2                     list_height,
 Boolean                  force_list,
 Boolean                  force_popup)

{
  SelectionDialogPtr  dlg;
  GrouP               p;
  ValNodePtr          vnp;
  Int4                num_choices;
  Int4                list_width = 8, item_width;

  if (choice_list == NULL)
  {
    return NULL;
  }
  
  dlg = (SelectionDialogPtr) MemNew (sizeof (SelectionDialogData));
  if (dlg == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (h, 0, 2, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);

  dlg->dialog = (DialoG) p;
  dlg->todialog = SelectionListToSelectionDialog;
  dlg->fromdialog = SelectionDialogToSelectionList;
  dlg->dialogmessage = SelectionDialogMessage;
  dlg->testdialog = TestSelectionDialog;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->err_msg = err_msg;
  
  num_choices = ValNodeLen (choice_list);
  
  if (allow_multi)
  {
    dlg->multi_select_dlg = MultiSelectDialog (p, choice_list, list_height,
                                               change_notify, change_userdata);
    dlg->num_choices = num_choices + 1;                                               
  }
  else
  {
    if (force_popup || (num_choices < 20 && ! force_list) || list_height == 1)
    {
      dlg->popup_ctrl = PopupList (p, TRUE, SelectionDialogPopupChanged);
      SetObjectExtra (dlg->popup_ctrl, dlg, NULL);
      for (vnp = choice_list; vnp != NULL; vnp = vnp->next) {
        PopupItem (dlg->popup_ctrl, vnp->data.ptrvalue);
      }
    }
    else
    {
      SelectFont (systemFont);
      for (vnp = choice_list; vnp != NULL; vnp = vnp->next) {
        item_width = StringWidth (vnp->data.ptrvalue);
        list_width = MAX (list_width, item_width);
      }
      /* add padding */
      list_width += StringWidth ("W");
      list_width = list_width / Nlm_stdCharWidth;
      dlg->list_ctrl = SingleList (p, list_width, list_height, SelectionDialogChanged);
      SetObjectExtra (dlg->list_ctrl, dlg, NULL);
      for (vnp = choice_list; vnp != NULL; vnp = vnp->next) {
        ListItem (dlg->list_ctrl, vnp->data.ptrvalue);
      }      
    }
    dlg->num_choices = num_choices;
  }
  
  return (DialoG) p;
}


extern DialoG SelectionDialogEx 
(GrouP h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 CharPtr                  err_msg,
 ValNodePtr               choice_list,
 Int2                     list_height,
 Boolean                  force_list)

{
  return SelectionDialogExEx (h, change_notify, change_userdata, allow_multi, err_msg, choice_list, list_height, force_list, FALSE);
}


extern DialoG SelectionDialog 
(GrouP h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 CharPtr                  err_msg,
 ValNodePtr               choice_list,
 Int2                     list_height)
{
  return SelectionDialogEx (h, change_notify, change_userdata, allow_multi,
                          err_msg, choice_list, list_height, FALSE);
}

typedef struct valnodeselection
{
  DIALOG_MESSAGE_BLOCK
  DialoG           list_dlg;
  ValNodePtr       choice_list;

  Boolean             is_multi;  
  FreeValNodeProc     free_vn_proc;
  CopyValNodeDataProc copy_vn_proc;
  MatchValNodeProc    match_vn_proc;
  RemapValNodeProc    remap_vn_proc;
  
} ValNodeSelectionData, PNTR ValNodeSelectionPtr;

static void ValNodeSelectionListToDialog (DialoG d, Pointer userdata)
{
  ValNodeSelectionPtr dlg;
  ValNodePtr          item_list, vnp_list, vnp_sel, pos_list = NULL;
  Int4                i;
  Boolean             found;

  dlg = (ValNodeSelectionPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  /* reset list control */
  PointerToDialog (dlg->list_dlg, NULL);  
  
  item_list = (ValNodePtr) userdata;
  for (vnp_list = item_list; vnp_list != NULL; vnp_list = vnp_list->next)
  {
    found = FALSE;
    i = 1;
    if (dlg->is_multi) {
      i++;
    }
    for (vnp_sel = dlg->choice_list;
         vnp_sel != NULL && !found;
         vnp_sel = vnp_sel->next, i++)
    {
      if ((dlg->match_vn_proc)(vnp_sel, vnp_list))
      {
        found = TRUE;
        ValNodeAddInt (&pos_list, 0, i);
      }
    }
  }
  PointerToDialog (dlg->list_dlg, pos_list);
  ValNodeFree (pos_list);  
}

static Pointer ValNodeSelectionDialogToList (DialoG d)
{
  ValNodeSelectionPtr dlg;
  ValNodePtr          item_list = NULL, vnp_list, pos_list, vnp_pos;
  ValNodePtr          vnp_copy, vnp_last = NULL, vnp_test;
  Int4                i;
  Boolean             found;

  dlg = (ValNodeSelectionPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  pos_list = DialogToPointer (dlg->list_dlg);
  for (vnp_pos = pos_list; vnp_pos != NULL; vnp_pos = vnp_pos->next)
  {
    for (i = 1, vnp_list = dlg->choice_list;
         i < vnp_pos->data.intvalue && vnp_list != NULL;
         i++, vnp_list = vnp_list->next)
    {
    }
    if (i == vnp_pos->data.intvalue && vnp_list != NULL)
    {
      /* make sure we don't already have this value in the list */
      for (vnp_test = item_list, found = FALSE;
           vnp_test != NULL && !found;
           vnp_test = vnp_test->next)
      {
        found = (dlg->match_vn_proc) (vnp_list, vnp_test);
      }
      
      if (found)
      {
        continue;
      }
      vnp_copy = (dlg->copy_vn_proc) (vnp_list);
      if (vnp_last == NULL)
      {
        item_list = vnp_copy;
      }
      else
      {
        vnp_last->next = vnp_copy;
      }
      vnp_last = vnp_copy;
    }
  }
  if (dlg->remap_vn_proc != NULL)
  {
    item_list = (dlg->remap_vn_proc) (item_list);
  }
  return item_list;  
}

static void CleanupValNodeSelectionDialogForm (GraphiC g, VoidPtr data)

{
  ValNodeSelectionPtr dlg;
  ValNodePtr          vnp;

  dlg = (ValNodeSelectionPtr) data;
  if (dlg != NULL) {
    if (dlg->free_vn_proc != NULL)
    {
      for (vnp = dlg->choice_list; vnp != NULL; vnp = vnp->next)
      {
        (dlg->free_vn_proc) (vnp);
      }
    }
    dlg->choice_list = ValNodeFree (dlg->choice_list);
  }
  StdCleanupExtraProc (g, data);
}

static void ValNodeSelectionDialogMessage (DialoG d, Int2 mssg)

{
  ValNodeSelectionPtr dlg;

  dlg = (ValNodeSelectionPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        PointerToDialog (dlg->list_dlg, NULL);
        break;
      case VIB_MSG_SELECT:
        SendMessageToDialog (dlg->list_dlg, VIB_MSG_SELECT);
        break;
      case VIB_MSG_ENTER :
        SendMessageToDialog (dlg->list_dlg, VIB_MSG_ENTER);
        break;
      case NUM_VIB_MSG + 1:
        SendMessageToDialog (dlg->list_dlg, NUM_VIB_MSG + 1);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestValNodeSelectionDialog (DialoG d)

{
  ValNodeSelectionPtr  dlg;
  ValNodePtr           head = NULL;

  dlg = (ValNodeSelectionPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    head = TestDialog (dlg->list_dlg);
  }
  return head;
}

extern DialoG ValNodeSelectionDialogExEx
(GrouP h,
 ValNodePtr               choice_list,
 Int2                     list_height,
 NameFromValNodeProc      name_proc,
 FreeValNodeProc          free_vn_proc,
 CopyValNodeDataProc      copy_vn_proc,
 MatchValNodeProc         match_vn_proc,
 CharPtr                  err_name,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 Boolean                  force_list,
 Boolean                  force_popup,
 RemapValNodeProc         remap_vn_proc)
{
  ValNodeSelectionPtr  dlg;
  GrouP                p;
  ValNodePtr           choice_name_list = NULL, vnp;

  if (choice_list == NULL || name_proc == NULL
      || copy_vn_proc == NULL || match_vn_proc == NULL)
  {
    return NULL;
  }
  
  dlg = (ValNodeSelectionPtr) MemNew (sizeof (ValNodeSelectionData));
  if (dlg == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (h, 1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupValNodeSelectionDialogForm);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = ValNodeSelectionListToDialog;
  dlg->fromdialog = ValNodeSelectionDialogToList;
  dlg->dialogmessage = ValNodeSelectionDialogMessage;
  dlg->testdialog = TestValNodeSelectionDialog;
  
  dlg->choice_list = choice_list;
  dlg->free_vn_proc = free_vn_proc;
  dlg->copy_vn_proc = copy_vn_proc;
  dlg->match_vn_proc = match_vn_proc;
  dlg->remap_vn_proc = remap_vn_proc;

  dlg->is_multi = allow_multi;

  for (vnp = choice_list; vnp != NULL; vnp = vnp->next)
  {
    ValNodeAddPointer (&choice_name_list, 0, (name_proc) (vnp));
  }

  dlg->list_dlg = SelectionDialogExEx (p, change_notify, change_userdata,
                                   allow_multi, err_name, choice_name_list, 
                                   list_height, force_list, force_popup);
  ValNodeFreeData (choice_name_list);  
  
  return (DialoG) p;
}


extern DialoG ValNodeSelectionDialogEx
(GrouP h,
 ValNodePtr               choice_list,
 Int2                     list_height,
 NameFromValNodeProc      name_proc,
 FreeValNodeProc          free_vn_proc,
 CopyValNodeDataProc      copy_vn_proc,
 MatchValNodeProc         match_vn_proc,
 CharPtr                  err_name,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 Boolean                  force_list,
 RemapValNodeProc         remap_vn_proc)
{
  return ValNodeSelectionDialogExEx (h, choice_list, list_height, name_proc, free_vn_proc, copy_vn_proc,
                                     match_vn_proc, err_name, change_notify, change_userdata, allow_multi,
                                     force_list, FALSE, remap_vn_proc);
}


extern DialoG ValNodeSelectionDialog
(GrouP h,
 ValNodePtr               choice_list,
 Int2                     list_height,
 NameFromValNodeProc      name_proc,
 FreeValNodeProc          free_vn_proc,
 CopyValNodeDataProc      copy_vn_proc,
 MatchValNodeProc         match_vn_proc,
 CharPtr                  err_name,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi)
{
  return ValNodeSelectionDialogEx (h, choice_list, list_height,
                                   name_proc, free_vn_proc,
                                   copy_vn_proc, match_vn_proc, err_name,
                                   change_notify, change_userdata,
                                   allow_multi, FALSE, NULL);
}


typedef struct stringcombodlg {
 DIALOG_MESSAGE_BLOCK

 DialoG list;
 TexT   text;
 Nlm_ChangeNotifyProc     change_notify;
 Pointer                  change_userdata;
} StringComboDlgData, PNTR StringComboDlgPtr;


static void ChangeStringComboDialogList (Pointer data)
{
  StringComboDlgPtr dlg;
  ValNodePtr        vnp;

  dlg = (StringComboDlgPtr) data;
  if (dlg == NULL) {
    return;
  }

  vnp = DialogToPointer (dlg->list);
  if (vnp != NULL && vnp->data.ptrvalue != NULL) {
    SetTitle (dlg->text, (CharPtr) vnp->data.ptrvalue);
  }
  vnp = ValNodeFree (vnp);
}


static void ChangeStringComboDialogText (TexT t)
{
  StringComboDlgPtr dlg;

  dlg = (StringComboDlgPtr) GetObjectExtra (t);
  if (dlg == NULL) {
    return;
  }

  /* to do - change list to match selection? */

  if (dlg->change_notify != NULL) {
    (dlg->change_notify)(dlg->change_userdata);
  }
}


static void StringToStringComboDialog (DialoG d, Pointer data)
{
  StringComboDlgPtr dlg;
  CharPtr           text;

  dlg = (StringComboDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return;
  }

  text = (CharPtr) data;
  if (text == NULL) {
    SetTitle (dlg->text, "");
  } else {
    SetTitle (dlg->text, text);
  }

  /* to do - pick from list also? */

}


static Pointer StringComboDialogToString (DialoG d)
{
  StringComboDlgPtr dlg;

  dlg = (StringComboDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return NULL;
  }

  return JustSaveStringFromText (dlg->text);
}

extern DialoG StringComboDialog
(GrouP h,
 ValNodePtr               choice_list,
 Int2                     list_height,
 Int2                     list_width,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  StringComboDlgPtr dlg;
  GrouP             p;

  dlg = (StringComboDlgPtr) MemNew (sizeof (StringComboDlgData));

  p = HiddenGroup (h, 0, 2, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToStringComboDialog;
  dlg->fromdialog = StringComboDialogToString;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->text = DialogText (p, "", list_width, ChangeStringComboDialogText);
  dlg->list = ValNodeSelectionDialog (p, choice_list, list_height, 
                                      ValNodeStringName,
                                      ValNodeSimpleDataFree,
                                      ValNodeStringCopy,
                                      ValNodeStringMatch,
                                      "combo",
                                      ChangeStringComboDialogList, dlg, FALSE);
  
  return (DialoG) p;
}


extern DialoG EnumAssocSelectionDialog 
(GrouP                 h,
 Nlm_EnumFieldAssocPtr eap,
 CharPtr               err_name,
 Boolean               allow_multi,
 Nlm_ChangeNotifyProc  change_notify,
 Pointer               change_userdata)

{
  DialoG     dlg;
  ValNodePtr choice_list = NULL;
  
  if (eap == NULL)
  {
    return NULL;
  }

  while (eap->name != NULL)
  {
    if (!StringHasNoText (eap->name))
    {
      ValNodeAddPointer (&choice_list, eap->value, StringSave (eap->name));
    }
    eap++;
  }
  
  /* note - the ValNodeSelectionDialog will free the qual_choice_list when done */                                            
  dlg = ValNodeSelectionDialog (h, choice_list, TALL_SELECTION_LIST, ValNodeStringName,
                                ValNodeSimpleDataFree, ValNodeStringCopy,
                                ValNodeChoiceMatch, err_name, 
                                change_notify, change_userdata, allow_multi);

  return dlg;
}

extern CharPtr ValNodeStringName (ValNodePtr vnp)
{
  if (vnp == NULL || vnp->data.ptrvalue == NULL)
  {
    return NULL;
  }
  else
  {
    return StringSave (vnp->data.ptrvalue);
  }
}

extern void ValNodeSimpleDataFree (ValNodePtr vnp)
{
  if (vnp != NULL && vnp->data.ptrvalue != NULL)
  {
    vnp->data.ptrvalue = MemFree (vnp->data.ptrvalue);
  }
}

extern ValNodePtr ValNodeStringCopy (ValNodePtr vnp)
{
  ValNodePtr vnp_copy = NULL;
  if (vnp != NULL)
  {
    ValNodeAddPointer (&vnp_copy, vnp->choice, StringSave (vnp->data.ptrvalue));
  }
  return vnp_copy;
}

extern Boolean ValNodeChoiceMatch (ValNodePtr vnp1, ValNodePtr vnp2)
{
  if (vnp1 == NULL || vnp2 == NULL)
  {
    return FALSE;
  }
  if (vnp1->choice == vnp2->choice)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

extern Boolean ValNodeStringMatch (ValNodePtr vnp1, ValNodePtr vnp2)
{
  if (vnp1 == NULL || vnp2 == NULL)
  {
    return FALSE;
  }
  if (StringCmp (vnp1->data.ptrvalue, vnp2->data.ptrvalue) == 0)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

typedef struct sequenceselection
{
  DIALOG_MESSAGE_BLOCK
  DialoG     sequence_list_dlg;
  ValNodePtr sequence_choice_list;
} SequenceSelectionData, PNTR SequenceSelectionPtr;

static void CleanupSequenceSelectionDialogForm (GraphiC g, VoidPtr data)

{
  SequenceSelectionPtr dlg;

  dlg = (SequenceSelectionPtr) data;
  if (dlg != NULL) {
    dlg->sequence_choice_list = ValNodeFree (dlg->sequence_choice_list);
  }
  StdCleanupExtraProc (g, data);
}

static void ResetSequenceSelectionDialog (SequenceSelectionPtr dlg)
{  
  if (dlg != NULL)
  {
    PointerToDialog (dlg->sequence_list_dlg, NULL);
  }
}

static void SequenceSelectionListToSequenceSelectionDialog (DialoG d, Pointer userdata)
{
  SequenceSelectionPtr dlg;
  ValNodePtr           sequence_list, vnp_list, vnp_sel, pos_list = NULL;
  Int4                 i;
  SeqIdPtr             sip;
  Boolean              found;

  dlg = (SequenceSelectionPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetSequenceSelectionDialog (dlg);  
  sequence_list = (ValNodePtr) userdata;
  for (vnp_list = sequence_list; vnp_list != NULL; vnp_list = vnp_list->next)
  {
    sip = (SeqIdPtr) vnp_list->data.ptrvalue;
    found = FALSE;
    while (sip != NULL && ! found)
    {
      for (vnp_sel = dlg->sequence_choice_list, i = 1;
           vnp_sel != NULL && !found;
           vnp_sel = vnp_sel->next, i++)
      {
        found = SeqIdIn (sip, vnp_sel->data.ptrvalue);
        if (found)
        {
          ValNodeAddInt (&pos_list, 0, i);
        }
      }
      sip = sip->next;
    }
  }
  PointerToDialog (dlg->sequence_list_dlg, pos_list);
  ValNodeFree (pos_list);
}

static Pointer SequenceSelectionDialogToSequenceSelectionList (DialoG d)
{
  SequenceSelectionPtr dlg;
  ValNodePtr           sequence_list = NULL, vnp_list, pos_list, vnp_pos;
  Int4                 i;

  dlg = (SequenceSelectionPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  pos_list = DialogToPointer (dlg->sequence_list_dlg);
  for (vnp_pos = pos_list; vnp_pos != NULL; vnp_pos = vnp_pos->next)
  {
    for (i = 1, vnp_list = dlg->sequence_choice_list;
         i < vnp_pos->data.intvalue && vnp_list != NULL;
         i++, vnp_list = vnp_list->next)
    {
    }
    if (i == vnp_pos->data.intvalue && vnp_list != NULL)
    {
      ValNodeAddPointer (&sequence_list, 0, vnp_list->data.ptrvalue);
    }
  }
  return sequence_list;
}

static void 
GetSequenceChoiceList 
(SeqEntryPtr sep,
 ValNodePtr PNTR list, 
 Boolean show_nucs, 
 Boolean show_prots)
{
  BioseqPtr                bsp;
  BioseqSetPtr             bssp;
  
  if (sep == NULL) return;
  
  if (IS_Bioseq (sep))
  {
    bsp = (BioseqPtr) sep->data.ptrvalue;
    if (bsp == NULL) return;
    if (!show_nucs && ISA_na (bsp->mol))
    {
      return;
    }
    if (!show_prots && ISA_aa (bsp->mol))
    {
      return;
    }
    ValNodeAddPointer (list, 0, bsp->id);
  }
  else
  {
      bssp = (BioseqSetPtr) sep->data.ptrvalue;
    for (sep = bssp->seq_set; sep != NULL; sep = sep->next) 
    {
      GetSequenceChoiceList (sep, list, show_nucs, show_prots);
    }
  }
}

static void SequenceSelectionDialogMessage (DialoG d, Int2 mssg)

{
  SequenceSelectionPtr dlg;

  dlg = (SequenceSelectionPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        ResetSequenceSelectionDialog (dlg);
        break;
      case VIB_MSG_SELECT:
        Select (dlg->sequence_list_dlg);
        break;
      case VIB_MSG_ENTER :
        Select (dlg->sequence_list_dlg);
        break;
      case NUM_VIB_MSG + 1:
        SendMessageToDialog (dlg->sequence_list_dlg, NUM_VIB_MSG + 1);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestSequenceSelectionDialog (DialoG d)

{
  SequenceSelectionPtr dlg;
  ValNodePtr           head = NULL;

  dlg = (SequenceSelectionPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    head = TestDialog (dlg->sequence_list_dlg);
  }
  return head;
}


extern DialoG SequenceSelectionDialogEx 
(GrouP h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 Boolean                  allow_none,
 Boolean                  show_nucs,
 Boolean                  show_prots,
 Uint2                    entityID,
 Int4                     list_height)

{
  SequenceSelectionPtr  dlg;
  GrouP                 p;
  ValNodePtr                vnp;
  SeqEntryPtr               sep;
  SeqIdPtr                  sip;
  Char                      tmp[128];
  ValNodePtr            choice_name_list = NULL;
  
  if (!show_nucs && ! show_prots)
  {
    return NULL;
  }
  
  sep = GetTopSeqEntryForEntityID (entityID);
  if (sep == NULL)
  {
    return NULL;
  }

  dlg = (SequenceSelectionPtr) MemNew (sizeof (SequenceSelectionData));
  if (dlg == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (h, 1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupSequenceSelectionDialogForm);

  dlg->dialog = (DialoG) p;
  dlg->todialog = SequenceSelectionListToSequenceSelectionDialog;
  dlg->fromdialog = SequenceSelectionDialogToSequenceSelectionList;
  dlg->dialogmessage = SequenceSelectionDialogMessage;
  dlg->testdialog = TestSequenceSelectionDialog;

  if (allow_none) {
    dlg->sequence_choice_list = ValNodeNew (NULL);
    dlg->sequence_choice_list->choice = 0;
    dlg->sequence_choice_list->data.ptrvalue = NULL;
  } else {
    dlg->sequence_choice_list = NULL;
  }
  GetSequenceChoiceList (sep, &dlg->sequence_choice_list, show_nucs, show_prots);
  
  
  for (vnp = dlg->sequence_choice_list; vnp != NULL; vnp = vnp->next) {
    sip = SeqIdFindWorst ((SeqIdPtr) vnp->data.ptrvalue);
    if (sip == NULL) {
      sprintf (tmp, " ");
    } else {
      SeqIdWrite (sip, tmp, PRINTID_REPORT, sizeof (tmp));
    }
    ValNodeAddPointer (&choice_name_list, 0, StringSave (tmp));
  }

  dlg->sequence_list_dlg = SelectionDialog (p, change_notify, change_userdata,
                                            allow_multi, "sequence",
                                            choice_name_list, list_height);
  ValNodeFreeData (choice_name_list); 
  return (DialoG) p;
}


extern DialoG SequenceSelectionDialog 
(GrouP h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 Boolean                  show_nucs,
 Boolean                  show_prots,
 Uint2                    entityID)
{
  return SequenceSelectionDialogEx (h, change_notify, change_userdata, allow_multi, FALSE, show_nucs, show_prots, entityID, TALL_SELECTION_LIST);
}


extern DialoG SubSourceTypeDialog 
(GrouP                    h,
 Int2                     list_height,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 Boolean                  force_list,
 Boolean                  include_note)
{
  ValNodePtr subsource_list = NULL;
  Int4       i;

  for (i = 0; current_subsource_subtype_alist[i].name != NULL; i++) {
    ValNodeAddPointer (&subsource_list, current_subsource_subtype_alist[i].value, StringSave (current_subsource_subtype_alist[i].name));
  }
  if (include_note) {
    ValNodeAddPointer (&subsource_list, SUBSRC_other, StringSave ("Note"));
  }
  
  return ValNodeSelectionDialogEx (h, subsource_list, list_height, 
                                   ValNodeStringName,
                                   ValNodeSimpleDataFree, 
                                   ValNodeStringCopy,
                                   ValNodeChoiceMatch, "subsource list", 
                                   change_notify, change_userdata, allow_multi, force_list, NULL);
}


extern DialoG OrgModTypeDialog 
(GrouP                    h,
 Int2                     list_height,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  allow_multi,
 Boolean                  force_list,
 Boolean                  include_note)
{
  ValNodePtr orgmod_list = NULL;
  Int4       i;

  for (i = 0; current_orgmod_subtype_alist[i].name != NULL; i++) {
    ValNodeAddPointer (&orgmod_list, current_orgmod_subtype_alist[i].value, StringSave (current_orgmod_subtype_alist[i].name));
  }
  if (include_note) {
    ValNodeAddPointer (&orgmod_list, ORGMOD_other, StringSave ("Note"));
  }
  
  return ValNodeSelectionDialogEx (h, orgmod_list, list_height, 
                                   ValNodeStringName,
                                   ValNodeSimpleDataFree, 
                                   ValNodeStringCopy,
                                   ValNodeChoiceMatch, "orgmod list", 
                                   change_notify, change_userdata, allow_multi, force_list, NULL);
}


/*
static CharPtr inferencePrefix [] = {
  "",
  "similar to sequence",
  "similar to AA sequence",
  "similar to DNA sequence",
  "similar to RNA sequence",
  "similar to RNA sequence, mRNA",
  "similar to RNA sequence, EST",
  "similar to RNA sequence, other RNA",
  "profile",
  "nucleotide motif",
  "protein motif",
  "ab initio prediction",
  NULL
};

ENUM_ALIST(inference_alist)
  { " ",                     0 },
  { "similar to sequence",   1 },
  { "similar to protein",    2 },
  { "similar to DNA",        3 },
  { "similar to RNA",        4 },
  { "similar to mRNA",       5 },
  { "similar to EST",        6 },
  { "similar to other RNA",  7 },
  { "profile",               8 },
  { "nucleotide motif",      9 },
  { "protein motif",        10 },
  { "ab initio prediction", 11 },
END_ENUM_ALIST

Uint2 inference_types [] = {
  TAGLIST_POPUP, TAGLIST_TEXT
};

Uint2 inference_widths [] = {
  0, 0
};

static EnumFieldAssocPtr inference_popups [] = {
  inference_alist, NULL
};

extern void GBQualsToInferenceDialog (DialoG d, SeqFeatPtr sfp)

{
  Int2               best;
  Char               ch;
  GBQualPtr          gbq;
  ValNodePtr         head = NULL;
  Int2               j;
  ValNodePtr         last = NULL;
  size_t             len;
  CharPtr            rest;
  CharPtr            str;
  TagListPtr         tlp;
  Char               tmp [32];
  ValNodePtr         vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp == NULL) return;

  if (sfp != NULL) {
    for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
      if (StringICmp (gbq->qual, "inference") != 0) continue;
      if (StringHasNoText (gbq->val)) continue;
      vnp = ValNodeNew (last);
      if (vnp == NULL) continue;
      if (head == NULL) {
        head = vnp;
      }
      last = vnp;

      rest = NULL;
      best = -1;
      for (j = 0; inferencePrefix [j] != NULL; j++) {
        len = StringLen (inferencePrefix [j]);
        if (StringNICmp (gbq->val, inferencePrefix [j], len) != 0) continue;
        rest = gbq->val + len;
        best = j;
      }
      if (best >= 0 && inferencePrefix [best] != NULL) {
        if (rest != NULL) {
          ch = *rest;
          while (IS_WHITESP (ch) || ch == ':') {
            rest++;
            ch = *rest;
          }
        }
        len = StringLen (rest);
        str = MemNew (len + 16);
        if (str != NULL) {
          sprintf (tmp, "%d", (int) best);
          StringCpy (str, tmp);
          StringCat (str, "\t");
          StringCat (str, rest);
          StringCat (str, "\n");
        }
        vnp->data.ptrvalue = str;
      } else {
        len + StringLen (gbq->val);
        str = MemNew (len + 8);
        if (str != NULL) {
          StringCpy (str, "0");
          StringCat (str, "\t");
          StringCat (str, gbq->val);
          StringCat (str, "\n");
        }
        vnp->data.ptrvalue = str;
      }
    }
  }

  SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
  tlp->vnp = head;
  SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
  for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) continue;
  tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
  CorrectBarMax (tlp->bar, tlp->max);
  CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
}

static void VisStringDialogToGbquals (SeqFeatPtr sfp, DialoG d, CharPtr qual)

{
  GBQualPtr   gbq, gbqlast = NULL;
  ValNodePtr  head = NULL, vnp;
  CharPtr     str;

  if (sfp == NULL || StringHasNoText (qual)) return;
  head = DialogToPointer (d);
  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    gbqlast = gbq;
  }
  for (vnp = head; vnp != NULL; vnp = vnp->next) {
    str = (CharPtr) vnp->data.ptrvalue;
    if (StringHasNoText (str)) continue;
    gbq = GBQualNew ();
    if (gbq == NULL) continue;
    gbq->qual = StringSave (qual);
    gbq->val = StringSave (str);
    if (gbqlast == NULL) {
      sfp->qual = gbq;
    } else {
      gbqlast->next = gbq;
    }
    gbqlast = gbq;
  }
  ValNodeFreeData (head);
}

extern void InferenceDialogToGBQuals (DialoG d, SeqFeatPtr sfp)

{
  GBQualPtr   gbq;
  GBQualPtr   gbqlast = NULL;
  Int2        j;
  size_t      len;
  CharPtr     prefix;
  CharPtr     ptr;
  CharPtr     rest;
  CharPtr     str;
  TagListPtr  tlp;
  Int2        val;
  ValNodePtr  vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp == NULL || sfp == NULL) return;

  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    gbqlast = gbq;
  }

  for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
    if (StringHasNoText ((CharPtr) vnp->data.ptrvalue)) continue;
    ptr = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
    TrimSpacesAroundString (ptr);
    prefix = NULL;
    if (StrToInt (ptr, &val)) {
      for (j = 0; inferencePrefix [j] != NULL; j++) {
        if (j == val) {
          prefix = inferencePrefix [j];
        }
      }
    }
    MemFree (ptr);
    rest = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 1);
    TrimSpacesAroundString (rest);
    if (StringDoesHaveText (prefix)) {
      len = StringLen (prefix) + StringLen (rest);
      str = (CharPtr) MemNew (len + 8);
      if (str != NULL) {
        if (StringDoesHaveText (prefix)) {
          StringCpy (str, prefix);
          if (StringDoesHaveText (rest)) {
            if (StringNICmp (rest, "(same species)", 14) != 0) {
              StringCat (str, ":");
            } else {
              StringCat (str, " ");
            }
          }
        }
        if (StringDoesHaveText (rest)) {
          StringCat (str, rest);
        }
        gbq = GBQualNew ();
        if (gbq != NULL) {
          gbq->qual = StringSave ("inference");
          gbq->val = str;
          if (gbqlast == NULL) {
            sfp->qual = gbq;
          } else {
            gbqlast->next = gbq;
          }
          gbqlast = gbq;
        }
      }
    }
    MemFree (rest);
  }
}

static DialoG CreateInferenceDialog (GrouP h, Uint2 rows, Int2 spacing, Int2 width)

{
  inference_widths [1] = width;
  return CreateTagListDialog (h, rows, 2, spacing,
                              inference_types, inference_widths,
                              inference_popups, NULL, NULL);
}
*/

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

/* inference dialog controls, utility functions */

Uint2 accessionlist_types [] = {
  TAGLIST_POPUP, TAGLIST_TEXT
};

Uint2 accessionlist_widths [] = {
  0, 10
};

ENUM_ALIST(accn_type_alist)
  { " ",       0 },
  { "GenBank", 1 },
  { "EMBL",    2 },
  { "DDBJ",    3 },
  { "INSD",    4 },
  { "RefSeq",  5 },
  { "UniProt", 6 },
  { "Other",   7 },
END_ENUM_ALIST

static EnumFieldAssocPtr accessionlist_popups [] = {
  accn_type_alist, NULL
};

static CharPtr accnTypePrefix [] = {
  "",
  "GenBank",
  "EMBL",
  "DDBJ",
  "INSD",
  "RefSeq",
  "UniProt",
  "?",
  NULL
};

const Int4 numAccnTypePrefixes = sizeof (accnTypePrefix) / sizeof (CharPtr);

static Int4 GetAccnTypeNum (CharPtr str)
{
  Int4 i;

  if (StringHasNoText (str)) return 0;

  for (i = 1; i < numAccnTypePrefixes; i++)
  {
    if (StringCmp (accnTypePrefix[i], str) == 0)
    {
      return i;
    }
  }
  return 0;
}


static CharPtr ValForOneAccession (CharPtr str)
{
  CharPtr cp, val_buf = NULL;
  CharPtr val_fmt = "%d\t%s";
  Int4    db;

  if (!StringHasNoText (str))
  {
    if (StringNCmp (str, ",?|", 3) == 0) {
      str += 3;
    } else if (StringNCmp (str, "?|", 2) == 0) {
      str += 2;
    }
    cp = StringChr (str, '|');
    if (cp == NULL)
    {
      if ((db = GetAccnTypeNum(str)) > 0)
      {
        val_buf = MemNew (sizeof (Char) * StringLen (val_fmt));
        sprintf (val_buf, val_fmt, db, " ");
      }
      else
      {
        val_buf = MemNew (sizeof (Char) * (StringLen (val_fmt) + StringLen (str)));
        sprintf (val_buf, val_fmt, 0, str);
      }
    }
    else
    {
      *cp = 0;
      db = GetAccnTypeNum (str);
      if (db > 0) {
        val_buf = MemNew (sizeof (Char) * (StringLen (val_fmt) + StringLen (cp + 1)));
        sprintf (val_buf, val_fmt, db, cp + 1);
        *cp = '|';
      } else {
        *cp = '|';
        db = 7;
        val_buf = MemNew (sizeof (Char) * (StringLen (val_fmt) + StringLen (str)));
        sprintf (val_buf, val_fmt, db, str);
      }
    }
  }
  return val_buf;
}

static void AccessionListDataToDialog (DialoG d, Pointer data)
{
  TagListPtr    tlp;
  CharPtr       str, cp, val_buf;
  ValNodePtr    new_list = NULL, vnp;
  Int4          j;
  Int2          scroll_pos;

  tlp = (TagListPtr) GetObjectExtra (d);
  
  if (tlp == NULL) return;
  str = (CharPtr) data;
  
  cp = StringChr (str, ',');
  while (cp != NULL)
  {
    *cp = 0;
    val_buf = ValForOneAccession (str);
    if (val_buf != NULL)
    {
      ValNodeAddPointer (&new_list, 0, val_buf);
    }
    *cp = ',';
    str = cp + 1;
    cp = StringChr (str, ',');
  }
  val_buf = ValForOneAccession (str);
  if (val_buf != NULL)
  {
    ValNodeAddPointer (&new_list, 0, val_buf);
  }

  scroll_pos = 0;
  if (tlp->bar != NULL) 
  {
    scroll_pos = GetBarValue (tlp->bar);
  }
  else if (tlp->left_bar != NULL)
  {
    scroll_pos = GetBarValue (tlp->left_bar);
  }

  SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
  tlp->vnp = new_list;
  for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
  }
  tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
  CorrectBarMax (tlp->bar, tlp->max);
  CorrectBarPage (tlp->bar, (Int2) (tlp->rows-1), (Int2) (tlp->rows-1));   

  /* retain scroll position */
  if (scroll_pos > tlp->max) {
    scroll_pos = tlp->max;
  }
  
  if (tlp->bar != NULL)
  {
    CorrectBarValue (tlp->bar, scroll_pos);      
  }
  if (tlp->left_bar != NULL)
  {
    CorrectBarValue (tlp->left_bar, scroll_pos);      
  }
    
  SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
  Update ();
 
}


static Pointer AccessionListDialogToData (DialoG d)
{
  TagListPtr    tlp;
  ValNodePtr    vnp;
  Int4          result_len = 0, db;
  CharPtr       str, acc_str, result_str = NULL;
  Boolean       first_item = TRUE;
  
  tlp = (TagListPtr) GetObjectExtra (d);
  
  if (tlp == NULL) return NULL;
  
  for (vnp = tlp->vnp;
       vnp != NULL;
       vnp = vnp->next)
  {
    acc_str = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 1);
    str = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
    if (!StringHasNoText (acc_str) || !StringHasNoText (str))
    {
      result_len += StringLen (acc_str);
      result_len += 1; /* comma */
      db = atoi (str);
      if (db >= 0 && db < numAccnTypePrefixes)
      {
        result_len += MAX (StringLen (accnTypePrefix[db]), 1);
      }
      else
      {
        result_len ++; /* will represent with ? */
      }
      result_len ++; /* for db/accession separator */
    }
    acc_str = MemFree (acc_str);
    str = MemFree (str);
  }
  
  if (result_len > 0) 
  {
    result_str = (CharPtr) MemNew (sizeof (Char) * (result_len + 1));
    for (vnp = tlp->vnp;
        vnp != NULL;
        vnp = vnp->next)
    {
      acc_str = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 1);
      str = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
      db = (str == NULL ? 0 : atoi (str));
      str = MemFree (str);
      if (!StringHasNoText (acc_str) || db > 0)
      {
        if (first_item)
        {
          first_item = FALSE;
        }
        else
        {
          StringCat (result_str, ",");
        }

        if (db > 0 && db != 7 && db < numAccnTypePrefixes)
        {
          StringCat (result_str, accnTypePrefix[db]);
          StringCat (result_str, "|");
        }
        if (!StringHasNoText (acc_str)) {
          StringCat (result_str, acc_str);
        }
      }
      acc_str = MemFree (acc_str);
    }
    result_str[result_len] = 0;
  }

  return result_str;
}


static void RemoveEmptyAccessionStrings (CharPtr acc_list)
{
  CharPtr cp_prev_end = NULL, cp_src, cp_dst;

  if (acc_list == NULL) return;
  
  cp_src = acc_list;
  cp_dst = acc_list;
  cp_prev_end = acc_list;
  while (*cp_src != 0)
  {
    if (*cp_src == '|' && (*(cp_src + 1) == ',' || *(cp_src + 1) == 0))
    {
      cp_dst = cp_prev_end;
      cp_prev_end = cp_dst;
      cp_src++;
    }
    else
    {
      *cp_dst = *cp_src;      
      if (*cp_src == ',')
      {
        cp_prev_end = cp_dst;
      }
      cp_dst++;
      cp_src++;
    }
  }
  *cp_dst = 0;
}


static CharPtr insdmessage =
"GenBank, EMBL, and DDBJ records are part of the International Nucleotide " \
"Sequence Database collaboration.\nThe database prefix for the /inference " \
"qualifier in these cases is INSD by collaboration policy.";


static Boolean ReplaceDatabaseStrings (CharPtr PNTR str)
{
  Boolean changed_db = FALSE;

  if (str == NULL || *str == NULL) return FALSE;

  if (StringSearch (*str, "GenBank|") != NULL)
  {
    changed_db = TRUE;
    FindReplaceString (str, "GenBank|", "INSD|", TRUE, FALSE);
  }
  if (StringSearch (*str, "EMBL|") != NULL)
  {
    changed_db = TRUE;
    FindReplaceString (str, "EMBL|", "INSD|", TRUE, FALSE);
  }
  if (StringSearch (*str, "DDBJ|") != NULL)
  {
    changed_db = TRUE;
    FindReplaceString (str, "DDBJ|", "INSD|", TRUE, FALSE);
  }

  if (changed_db)
  {
    if (GetAppProperty ("InternalNcbiSequin") == NULL) {
      Message (MSG_OK, "%s", insdmessage);
    }
  }

  return changed_db;
}


static void ChangeInferAccessionList (Pointer data); 
static TaglistCallback AccessionListCallbacks[] = 
{ ChangeInferAccessionList, ChangeInferAccessionList };

typedef struct inferevid {
  CharPtr  evcat;      /* from inferencePrefix     */
  CharPtr  prefix;     /* from inferencePrefix     */
  Boolean  species;    /* optional (same species)  */
  CharPtr  database;   /* INSD, RefSeq, etc.       */
  CharPtr  db_other;   /* other database           */
  CharPtr  accession;  /* accession.version        */
  CharPtr  program;    /* common analysis program  */
  CharPtr  pr_other;   /* other program            */
  CharPtr  version;    /* program version          */
  CharPtr  basis1;     /* profile or motif         */
  CharPtr  basis2;     /*  evidence_basis texts    */
  CharPtr  accession_list; /* accession list for alignment */
} InferEvid, PNTR InferEvidPtr;

typedef struct inferdialog {
  DIALOG_MESSAGE_BLOCK

  DoC           inferdoc;
  Int2          currItem;

  PopuP         evcat;
  PopuP         prefix;
  ButtoN        species;
  PopuP         database;
  TexT          db_other;
  TexT          accession;
  PopuP         program;
  TexT          pr_other;
  TexT          version;
  TexT          basis1;
  TexT          basis2;
  PrompT        inf_free_program_prompt;
  PrompT        inf_free_version_prompt;
  PrompT        accession_list_program_prompt;
  PrompT        accession_list_version_prompt;
  PrompT        accession_list_prompt;
  DialoG        accession_list;

  GrouP         inf_accn_group;
  GrouP         other_db_group;
  GrouP         inf_prog_group;
  GrouP         other_pr_group;
  GrouP         inf_free_group;

  Int2          numInf;
  InferEvidPtr  evidence [128];

} InferDialog, PNTR InferDialogPtr;

static InferEvidPtr InferEvidNew (
  void
)

{
  InferEvidPtr  iep;

  iep = MemNew (sizeof (InferEvid));
  if (iep == NULL) return NULL;

  return iep;
}

static InferEvidPtr InferEvidFree (
  InferEvidPtr iep
)

{
  if (iep == NULL) return NULL;

  MemFree (iep->evcat);
  MemFree (iep->prefix);
  MemFree (iep->database);
  MemFree (iep->accession);
  MemFree (iep->program);
  MemFree (iep->version);
  MemFree (iep->basis1);
  MemFree (iep->basis2);
  MemFree (iep->accession_list);

  return MemFree (iep);
}

static InferEvidPtr GetInferEvid (
  InferDialogPtr idp,
  Int2 item
)

{
  InferEvidPtr  iep;

  if (idp == NULL || item < 0 || item > 127) return NULL;
  iep = idp->evidence [item];
  if (iep != NULL) return iep;

  iep = InferEvidNew ();
  if (iep != NULL) {
    /*
    iep->evcat = StringSave (" ");
    iep->prefix = StringSave (" ");
    iep->database = StringSave (" ");
    iep->db_other = StringSave ("");
    iep->accession = StringSave ("");
    iep->program = StringSave (" ");
    iep->pr_other = StringSave ("");
    iep->version = StringSave ("");
    iep->basis1 = StringSave ("");
    iep->basis2 = StringSave ("");
    */
  }
  idp->evidence [item] = iep;
  return iep;
}

/* inference DoC object tables */

#define NUM_INFERENCE_LINES 3

static ParData  inferParFmt = { FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 };

static ColData  inferColFmt [] = {
  {0, 5, 25, 0, NULL, 'l', FALSE, FALSE, FALSE, FALSE, FALSE}, /* class     */
  {0, 5, 25, 2, NULL, 'l', FALSE, TRUE,  FALSE, FALSE, TRUE}   /* specifics */
};

static CharPtr evcatPrefix [] = {
  "",
  "COORDINATES: ",
  "DESCRIPTION: ",
  "EXISTENCE: ",
  NULL
};

ENUM_ALIST(evcat_alist)
  { " ",             0 },
  { "COORDINATES",   1 },
  { "DESCRIPTION",   2 },
  { "EXISTENCE",     3 },
END_ENUM_ALIST

static CharPtr inferencePrefix [] = {
  "",
  "similar to sequence",
  "similar to AA sequence",
  "similar to DNA sequence",
  "similar to RNA sequence",
  "similar to RNA sequence, mRNA",
  "similar to RNA sequence, EST",
  "similar to RNA sequence, other RNA",
  "profile",
  "nucleotide motif",
  "protein motif",
  "ab initio prediction",
  "alignment",
  NULL
};

ENUM_ALIST(inference_alist)
  { " ",                     0 },
  { "similar to sequence",   1 },
  { "similar to protein",    2 },
  { "similar to DNA",        3 },
  { "similar to RNA",        4 },
  { "similar to mRNA",       5 },
  { "similar to EST",        6 },
  { "similar to other RNA",  7 },
  { "profile",               8 },
  { "nucleotide motif",      9 },
  { "protein motif",        10 },
  { "ab initio prediction", 11 },
  { "alignment",            12 },
END_ENUM_ALIST

static CharPtr programPrefix [] = {
  "",
  "tRNAscan",
  "Genscan",
  "?",
  NULL
};

ENUM_ALIST(program_alist)
  { " ",        0 },
  { "tRNAscan", 1 },
  { "Genscan",  2 },
  { "Other",    3 },
END_ENUM_ALIST

static CharPtr PrintInferTable (
  DoC d,
  Int2 item,
  Pointer data
)

{
  CharPtr         buf;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  size_t          len;

  idp = (InferDialogPtr) GetObjectExtra (d);
  if (idp == NULL || item < 1 || item > 127) return NULL;
  iep = GetInferEvid (idp, item);
  if (iep == NULL) return NULL;

  len = StringLen (iep->prefix) + StringLen (iep->database) + StringLen (iep->db_other) + StringLen (iep->accession) +
        StringLen (iep->program) + StringLen (iep->pr_other) + StringLen (iep->version) + StringLen (iep->basis1) +
        StringLen (iep->basis2) + StringLen (iep->accession_list) + StringLen (iep->evcat) + 50;
  buf = MemNew (len);
  if (buf == NULL) return NULL;

  if (StringHasNoText (iep->prefix)) {
    StringCat (buf, " \t \n");
    return buf;
  }

  StringCat (buf, iep->prefix);

  StringCat (buf, "\t");

  if (StringNICmp (iep->prefix, "similar to ", 11) == 0) {
    if (StringDoesHaveText (iep->accession)) {
      if (StringCmp (iep->database, "Other") == 0) {
        if (StringDoesHaveText (iep->db_other)) {
          StringCat (buf, iep->db_other);
          StringCat (buf, ":");
        }
      } else if (StringDoesHaveText (iep->database)) {
        StringCat (buf, iep->database);
        StringCat (buf, ":");
      }
      StringCat (buf, iep->accession);
    }
  } else if (StringNICmp (iep->prefix, "ab initio ", 10) == 0) {
    if (StringCmp (iep->program, "Other") == 0) {
      if (StringDoesHaveText (iep->pr_other)) {
        StringCat (buf, iep->pr_other);
        if (StringDoesHaveText (iep->version)) {
          StringCat (buf, ":");
          StringCat (buf, iep->version);
        }
      }
    } else if (StringDoesHaveText (iep->program)) {
      StringCat (buf, iep->program);
      if (StringDoesHaveText (iep->version)) {
        StringCat (buf, ":");
        StringCat (buf, iep->version);
      }
    }
  } else if (StringDoesHaveText (iep->basis1)) {
    StringCat (buf, iep->basis1);
    if (StringDoesHaveText (iep->basis2)) {
      StringCat (buf, ":");
      StringCat (buf, iep->basis2);
      if (StringCmp (iep->prefix, "alignment") == 0 && 
          StringDoesHaveText (iep->accession_list)) {
        StringCat (buf, ":");
        StringCat (buf, iep->accession_list);
      }
    }
  } else {
    StringCat (buf, " ");
  }

  StringCat (buf, "\n");
  return buf;
}

static void ShowInferenceGroup (
  InferDialogPtr idp
)

{
  CharPtr  str;
  UIEnum   val;

  if (idp == NULL) return;
  if (GetEnumPopup (idp->prefix, inference_alist, &val)) {
    if (val >= 1 && val <= 7) {
      SafeHide (idp->inf_prog_group);
      SafeHide (idp->inf_free_group);
      SafeShow (idp->inf_accn_group);
      SafeShow (idp->species);
      str = GetEnumPopupByName (idp->database, accn_type_alist);
      if (StringCmp (str, "Other") == 0) {
        SafeShow (idp->other_db_group);
      } else {
        SafeHide (idp->other_db_group);
      }
      MemFree (str);
    } else if (val >= 8 && val <= 10) {
      SafeHide (idp->inf_accn_group);
      SafeHide (idp->species);
      SafeHide (idp->inf_prog_group);
      SafeShow (idp->inf_free_group);
      SafeHide (idp->accession_list);
      SafeShow (idp->inf_free_program_prompt);
      SafeShow (idp->inf_free_version_prompt);
      SafeHide (idp->accession_list_program_prompt);
      SafeHide (idp->accession_list_version_prompt);
      SafeHide (idp->accession_list_prompt);
    } else if (val == 11) {
      SafeHide (idp->inf_accn_group);
      SafeHide (idp->species);
      SafeHide (idp->inf_free_group);
      SafeShow (idp->inf_prog_group);
      str = GetEnumPopupByName (idp->program, program_alist);
      if (StringCmp (str, "Other") == 0) {
        SafeShow (idp->other_pr_group);
      } else {
        SafeHide (idp->other_pr_group);
      }
      MemFree (str);
    } else if (val == 12) {
      SafeHide (idp->inf_accn_group);
      SafeHide (idp->species);
      SafeHide (idp->inf_prog_group);
      SafeShow (idp->inf_free_group);
      SafeShow (idp->accession_list);
      SafeHide (idp->inf_free_program_prompt);
      SafeHide (idp->inf_free_version_prompt);
      SafeShow (idp->accession_list_program_prompt);
      SafeShow (idp->accession_list_version_prompt);
      SafeShow (idp->accession_list_prompt);
    } else {
      SafeHide (idp->inf_accn_group);
      SafeHide (idp->species);
      SafeHide (idp->inf_prog_group);
      SafeHide (idp->inf_free_group);
    }
  } else {
    SafeHide (idp->inf_accn_group);
    SafeHide (idp->species);
    SafeHide (idp->inf_prog_group);
    SafeHide (idp->inf_free_group);
  }
  Update ();
}

static void SafeSetEnumPopupByName (PopuP lst, EnumFieldAssocPtr al, CharPtr name)

{
  if (StringDoesHaveText (name)) {
    SetEnumPopupByName (lst, al, name);
  } else {
    SetEnumPopupByName (lst, al, " ");
  }
}

static void ChangeInferTableSelect (
  DoC d,
  Int2 item,
  Int2 row,
  Int2 col,
  Boolean dblClck
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  Int2            itemOld1, itemOld2;

  idp = (InferDialogPtr) GetObjectExtra (d);
  if (idp == NULL) return;
  if (item == 0 || row == 0 || col == 0) return;

  GetDocHighlight (d, &itemOld1, &itemOld2);
  SetDocHighlight (d, item, item);
  UpdateDocument (d, itemOld1, itemOld2);
  UpdateDocument (d, item, item);
  idp->currItem = item;

  iep = GetInferEvid (idp, item);
  if (iep != NULL) {
    ResetClip ();
    SafeSetEnumPopupByName (idp->evcat, evcat_alist, iep->evcat);
    SafeSetEnumPopupByName (idp->prefix, inference_alist, iep->prefix);

    SafeSetStatus (idp->species, iep->species);
    SafeSetEnumPopupByName (idp->database, accn_type_alist, iep->database);
    SafeSetTitle (idp->db_other, iep->db_other);
    SafeSetTitle (idp->accession, iep->accession);

    SafeSetEnumPopupByName (idp->program, program_alist, iep->program);
    SafeSetTitle (idp->pr_other, iep->pr_other);
    SafeSetTitle (idp->version, iep->version);

    SafeSetTitle (idp->basis1, iep->basis1);
    SafeSetTitle (idp->basis2, iep->basis2);

    ReplaceDatabaseStrings (&(iep->accession_list));
    PointerToDialog (idp->accession_list, iep->accession_list);

    ShowInferenceGroup (idp);
  }

  Update ();
}

static void CheckExtendInferTable (
  InferDialogPtr idp
)

{
  Int2  numItems;

  if (idp == NULL) return;

  GetDocParams (idp->inferdoc, &numItems, NULL);
  if (idp->currItem == numItems) {
    AppendItem (idp->inferdoc, PrintInferTable, idp, FALSE, 1,
                &inferParFmt, inferColFmt, systemFont);
  }

  Update ();
}

static void ChangeInferCat (
  PopuP p
)

{
  AlistDialogPtr  adp;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  CharPtr         str;

  adp = (AlistDialogPtr) GetObjectExtra (p);
  if (adp == NULL) return;
  idp = (InferDialogPtr) adp->userdata;
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  str = GetEnumPopupByName (idp->evcat, evcat_alist);
  iep->evcat = MemFree (iep->evcat);
  iep->evcat = str; /* allocated by GetEnumPopupByName */

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferPrefix (
  PopuP p
)

{
  AlistDialogPtr  adp;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  CharPtr         str;

  adp = (AlistDialogPtr) GetObjectExtra (p);
  if (adp == NULL) return;
  idp = (InferDialogPtr) adp->userdata;
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  str = GetEnumPopupByName (idp->prefix, inference_alist);
  iep->prefix = MemFree (iep->prefix);
  iep->prefix = str; /* allocated by GetEnumPopupByName */

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeSameSpecies (
  ButtoN b
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (b);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->species = (Boolean) (GetStatus (b));

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferDatabase (
  PopuP p
)


{
  AlistDialogPtr  adp;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  CharPtr         str;

  adp = (AlistDialogPtr) GetObjectExtra (p);
  if (adp == NULL) return;
  idp = (InferDialogPtr) adp->userdata;
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  str = GetEnumPopupByName (idp->database, accn_type_alist);
  if (StringCmp (str, "GenBank") == 0 ||
      StringCmp (str, "EMBL") == 0 ||
      StringCmp (str, "DDBJ") == 0) {
    if (GetAppProperty ("InternalNcbiSequin") == NULL) {
      Message (MSG_OK, "%s", insdmessage);
    }
    SetEnumPopupByName (idp->database, accn_type_alist, "INSD");
    str = MemFree (str);
    str = StringSave ("INSD");
  }
  iep->database = MemFree (iep->database);
  iep->database = str; /* allocated by GetEnumPopupByName */

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferDbOther (
  TexT t
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (t);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->db_other = MemFree (iep->db_other);
  iep->db_other = SaveStringFromText (t);

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferAccession (
  TexT t
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (t);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->accession = MemFree (iep->accession);
  iep->accession = SaveStringFromText (t);

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferProgram (
  PopuP p
)


{
  AlistDialogPtr  adp;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  CharPtr         str;

  adp = (AlistDialogPtr) GetObjectExtra (p);
  if (adp == NULL) return;
  idp = (InferDialogPtr) adp->userdata;
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  str = GetEnumPopupByName (idp->program, program_alist);
  iep->program = MemFree (iep->program);
  iep->program = str; /* allocated by GetEnumPopupByName */

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferPrOther (
  TexT t
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (t);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->pr_other = MemFree (iep->pr_other);
  iep->pr_other = SaveStringFromText (t);

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferVersion (
  TexT t
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (t);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->version = MemFree (iep->version);
  iep->version = SaveStringFromText (t);

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferBasis1 (
  TexT t
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (t);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->basis1 = MemFree (iep->basis1);
  iep->basis1 = SaveStringFromText (t);

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}

static void ChangeInferBasis2 (
  TexT t
)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) GetObjectExtra (t);
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->basis2 = MemFree (iep->basis2);
  iep->basis2 = SaveStringFromText (t);

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}


static void ChangeInferAccessionList (Pointer data)
{
  InferDialogPtr  idp;
  InferEvidPtr    iep;

  idp = (InferDialogPtr) data;
  if (idp == NULL) return;
  iep = GetInferEvid (idp, idp->currItem);
  if (iep == NULL) return;

  iep->accession_list = MemFree (iep->accession_list);
  iep->accession_list = DialogToPointer (idp->accession_list);
  
  if (ReplaceDatabaseStrings (&(iep->accession_list)))
  {
    PointerToDialog (idp->accession_list, iep->accession_list);
  }

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, idp->currItem, idp->currItem);
  Update ();

  CheckExtendInferTable (idp);
}


static Boolean StringInList (CharPtr str, CharPtr PNTR list)

{
  Int2  i;

  if (str == NULL || list == NULL) return FALSE;

  for (i = 0; list [i] != NULL; i++) {
    if (StringICmp (str, list[i]) == 0) return TRUE;
  }

  return FALSE;
}

extern void GBQualsToInferenceDialog (DialoG d, SeqFeatPtr sfp)

{
  Int2            best;
  Char            ch;
  Int2            evc;
  GBQualPtr       gbq;
  CharPtr         gbval;
  Int2            i, j, k;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  size_t          len;
  CharPtr         rest;
  CharPtr         str;
  CharPtr         tmp, tmp2;

  idp = (InferDialogPtr) GetObjectExtra (d);
  if (idp == NULL) return;

  if (sfp == NULL || sfp->qual == NULL) {
    Reset (idp->inferdoc);
    SetValue (idp->evcat, 0);
    SetValue (idp->prefix, 0);
    SetStatus (idp->species, FALSE);
    SetValue (idp->database, 0);
    SetTitle (idp->db_other, "");
    SetTitle (idp->accession, "");
    SetValue (idp->program, 0);
    SetTitle (idp->pr_other, "");
    SetTitle (idp->version, "");
    SetTitle (idp->basis1, "");
    SetTitle (idp->basis2, "");
    SafeHide (idp->inf_accn_group);
    SafeHide (idp->inf_prog_group);
    SafeHide (idp->inf_free_group);
    idp->numInf = 0;
    idp->currItem = 1;
    for (i = 0; i < NUM_INFERENCE_LINES; i++) {
      AppendItem (idp->inferdoc, PrintInferTable, idp, FALSE, 1,
                  &inferParFmt, inferColFmt, systemFont);
    }
    SetDocHighlight (idp->inferdoc, 1, 1);
    return;
  }

  idp->numInf = 0;
  idp->currItem = 1;
  Reset (idp->inferdoc);

  for (k = 0; k < 128; k++) {
    iep = idp->evidence [k];
    InferEvidFree (iep);
    idp->evidence [k] = NULL;
  }

  for (gbq = sfp->qual, k = 0; gbq != NULL; gbq = gbq->next) {
    if (StringICmp (gbq->qual, "inference") != 0) continue;
    if (StringHasNoText (gbq->val)) continue;

    gbval = gbq->val;
    evc = -1;
    for (j = 0; evcatPrefix [j] != NULL; j++) {
      len = StringLen (evcatPrefix [j]);
      if (StringNICmp (gbq->val, evcatPrefix [j], len) != 0) continue;
      gbval = gbq->val + len;
      evc = j;
    }

    rest = NULL;
    best = -1;
    for (j = 0; inferencePrefix [j] != NULL; j++) {
      len = StringLen (inferencePrefix [j]);
      if (StringNICmp (gbval, inferencePrefix [j], len) != 0) continue;
      rest = gbval + len;
      best = j;
    }

    k++;
    iep = GetInferEvid (idp, k);
    if (iep == NULL) continue;

    str = NULL;
    if (best > 0 && inferencePrefix [best] != NULL) {
      iep->prefix = MemFree (iep->prefix);
      iep->prefix = StringSave(GetEnumName ((UIEnum) best, inference_alist));

      if (evc > 0 && evcatPrefix [evc] != NULL) {
        iep->evcat = MemFree (iep->evcat);
        iep->evcat = StringSave(GetEnumName ((UIEnum) evc, evcat_alist));
      }

      if (rest != NULL) {
        ch = *rest;
        while (IS_WHITESP (ch)) {
          rest++;
          ch = *rest;
        }
        if (StringNICmp (rest, "(same species)", 14) == 0) {
          iep->species = TRUE;
          rest += 14;
        } else {
          iep->species = FALSE;
        }
        ch = *rest;
        while (IS_WHITESP (ch) || ch == ':') {
          rest++;
          ch = *rest;
        }
      }
      if (StringDoesHaveText (rest)) {
        str = StringSave (rest);
      }
      tmp = StringChr (str, ':');
      if (tmp != NULL) {
        *tmp = '\0';
        tmp++;
        TrimSpacesAroundString (str);
        TrimSpacesAroundString (tmp);
      } else {
        TrimSpacesAroundString (str);
      }
      if (StringNICmp (iep->prefix, "similar to ", 11) == 0) {
        if (StringInList (str, accnTypePrefix)) {
          iep->database = MemFree (iep->database);
          iep->database = StringSaveNoNull (str);
          iep->accession = MemFree (iep->accession);
          iep->accession = StringSaveNoNull (tmp);
        } else if (tmp != NULL) {
          iep->database = MemFree (iep->database);
          iep->database = StringSaveNoNull ("Other");
          iep->db_other = MemFree (iep->db_other);
          iep->db_other = StringSaveNoNull (str);
          iep->accession = MemFree (iep->accession);
          iep->accession = StringSaveNoNull (tmp);
        } else {
          iep->database = MemFree (iep->database);
          iep->database = StringSaveNoNull (" ");
          iep->db_other = MemFree (iep->db_other);
          iep->accession = MemFree (iep->accession);
          iep->accession = StringSaveNoNull (str);
        }
      } else if (StringNICmp (iep->prefix, "ab initio ", 10) == 0) {
        if (StringInList (str, programPrefix)) {
          iep->program = MemFree (iep->program);
          iep->program = StringSaveNoNull (str);
        } else {
          iep->program = MemFree (iep->program);
          iep->program = StringSaveNoNull ("Other");
          iep->pr_other = MemFree (iep->pr_other);
          iep->pr_other = StringSaveNoNull (str);
        }
        iep->version = MemFree (iep->version);
        iep->version = StringSaveNoNull (tmp);
      } else {
        iep->basis1 = MemFree (iep->basis1);
        iep->basis1 = StringSaveNoNull (str);
        tmp2 = NULL;
        if (StringCmp (iep->prefix, "alignment") == 0)
        {
          tmp2 = StringChr (tmp, ':');
          if (tmp2 != NULL) {
            *tmp2 = 0;
            tmp2++;
          }
        }
        iep->basis2 = MemFree (iep->basis2);
        iep->basis2 = StringSaveNoNull (tmp);
        iep->accession_list = MemFree (iep->accession_list);
        iep->accession_list = StringSaveNoNull (tmp2);
      }

    } else {
      iep->evcat = NULL;
      iep->prefix = StringSave ("???");
      str = StringSave (gbval);
      tmp = StringChr (str, ':');
      if (tmp != NULL) {
        *tmp = '\0';
        tmp++;
        TrimSpacesAroundString (str);
        TrimSpacesAroundString (tmp);
      } else {
        TrimSpacesAroundString (str);
      }
      iep->basis1 = MemFree (iep->basis1);
      iep->basis1 = StringSaveNoNull (str);
      iep->basis2 = MemFree (iep->basis2);
      iep->basis2 = StringSaveNoNull (tmp);
    }

    MemFree (str);

    AppendItem (idp->inferdoc, PrintInferTable, idp, FALSE, 1,
                &inferParFmt, inferColFmt, systemFont);

    (idp->numInf)++;
  }

  AppendItem (idp->inferdoc, PrintInferTable, idp, FALSE, 1,
              &inferParFmt, inferColFmt, systemFont);
  k++;

  while (k < NUM_INFERENCE_LINES) {
    AppendItem (idp->inferdoc, PrintInferTable, idp, FALSE, 1,
                &inferParFmt, inferColFmt, systemFont);
    k++;
  }

  ShowInferenceGroup (idp);

  UpdateDocument (idp->inferdoc, 0, 0);

  ChangeInferTableSelect (idp->inferdoc, 1, 1, 1, FALSE);

  Update ();
}

extern void InferenceDialogToGBQuals (DialoG d, SeqFeatPtr sfp, Boolean convertBadToNote)

{
  CharPtr         first = NULL, second = NULL, accession_list = NULL;
  CharPtr         evcat = NULL;
  GBQualPtr       gbq, lastgbq;
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  Int2            k, numItems;
  size_t          len;
  CharPtr         prefix = NULL;
  CharPtr         speciesies = NULL;
  CharPtr         str;
  UIEnum          val;

  idp = (InferDialogPtr) GetObjectExtra (d);
  if (idp == NULL || sfp == NULL) return;

  lastgbq = NULL;
  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    lastgbq = gbq;
  }

  GetDocParams (idp->inferdoc, &numItems, NULL);
  for (k = 1; k <= numItems; k++) {
    iep = GetInferEvid (idp, k);
    if (iep == NULL) continue;

    if (StringHasNoText (iep->prefix)) continue;
    gbq = GBQualNew ();
    if (gbq == NULL) continue;

    gbq->qual = StringSave ("inference");

    evcat = NULL;
    if (WhereInEnumPopup (evcat_alist, iep->evcat, &val)) {
      if (val > 0 && val <= 3) {
        evcat = evcatPrefix [(int) val];
      }
    }
    prefix = NULL;
    if (WhereInEnumPopup (inference_alist, iep->prefix, &val)) {
      if (val > 0 && val <= 12) {
        prefix = inferencePrefix [(int) val];
      }
    }
    speciesies = NULL;
    if (StringNICmp (iep->prefix, "similar to ", 11) == 0) {
      if (iep->species) {
        speciesies = " (same species)";
      }
      if (StringDoesHaveText (iep->accession)) {
        if (StringCmp (iep->database, "Other") == 0) {
          if (StringDoesHaveText (iep->db_other)) {
            first = iep->db_other;
          }
        } else if (StringDoesHaveText (iep->database)) {
          first = iep->database;
        }
        second = iep->accession;
      }
    } else if (StringNICmp (iep->prefix, "ab initio ", 10) == 0) {
      if (StringCmp (iep->program, "Other") == 0) {
        if (StringDoesHaveText (iep->pr_other)) {
          first = iep->pr_other;
        }
      } else if (StringDoesHaveText (iep->program)) {
        first = iep->program;
      }
      second = iep->version;
    } else {
      if (StringDoesHaveText (iep->basis1)) {
        first = iep->basis1;
        second = iep->basis2;
        if (StringCmp (iep->prefix, "alignment") == 0) {
          accession_list = iep->accession_list;
          RemoveEmptyAccessionStrings (accession_list);
          ReplaceDatabaseStrings (&accession_list);
        }
      }
    }

    len = StringLen (evcat) + StringLen (prefix) + StringLen (speciesies) +
          StringLen (first) + StringLen (second) + StringLen (accession_list);
    str = MemNew (len + 6);
    if (str != NULL) {
      str [0] = '\0';
      if (evcat != NULL) {
        StringCat (str, evcat);
      }
      StringCat (str, prefix);
      StringCat (str, speciesies);
      StringCat (str, ":");
      StringCat (str, first);
      StringCat (str, ":");
      StringCat (str, second);
      if (StringCmp (prefix, "alignment") == 0 && StringDoesHaveText (accession_list)) {
        StringCat (str, ":");
        StringCat (str, accession_list);
      }
      gbq->val = StringSave (str);
      MemFree (str);
    } else {
      gbq->val = StringSave ("?");
    }

    /* do not allow saving of bad qualifier */
    if (convertBadToNote &&
        ValidateInferenceQualifier (gbq->val, FALSE) != VALID_INFERENCE) {
      if (StringNICmp (gbq->val, "similar to ", 11) == 0) {
        len = StringLen ("similar to ") + StringLen (first) + StringLen (second);
        str = MemNew (len + 5);
        if (str != NULL) {
          StringCpy (str, "similar to ");
          if (StringDoesHaveText (first)) {
            StringCat (str, first);
            if (StringDoesHaveText (second)) {
              StringCat (str, ":");
              StringCat (str, second);
            }
          } else if (StringDoesHaveText (second)) {
            StringCat (str, second);
          }
          gbq->val = MemFree (gbq->val);
          gbq->val = StringSave (str);
          MemFree (str);
        }
      }
      gbq->qual = MemFree (gbq->qual);
      gbq->qual = StringSave ("note");
    }

    if (sfp->qual == NULL) {
      sfp->qual = gbq;
    }
    if (lastgbq != NULL) {
      lastgbq->next = gbq;
    }
    lastgbq = gbq;
    accession_list = NULL;
  }
}

static void CleanupInferProc (GraphiC g, VoidPtr data)

{
  InferDialogPtr  idp;
  InferEvidPtr    iep;
  Int2            k;

  idp = (InferDialogPtr) data;
  if (idp != NULL) {
    for (k = 0; k < 128; k++) {
      iep = idp->evidence [k];
      InferEvidFree (iep);
      idp->evidence [k] = NULL;
    }
  }
  StdCleanupExtraProc (g, data);
}

static DialoG NewCreateInferenceDialog (
  GrouP prnt
)

{
  GrouP           cts, tbl, g0, g1, g2, g3, g4, g5, p, prompt_grp;
  FonT            fnt;
  Int2            i, hgt, wid;
  InferDialogPtr  idp;

  idp = (InferDialogPtr) MemNew (sizeof (InferDialog));
  if (idp == NULL) return NULL;

  p = HiddenGroup (prnt, -1, 0, NULL);
  SetGroupSpacing (p, 10, 10);

  SetObjectExtra (p, idp, CleanupInferProc);
  idp->dialog = (DialoG) p;
  /*
  idp->todialog = GBQualToInferTable;
  idp->fromdialog = InferTableToGBQual;
  */

  SelectFont (systemFont);
  hgt = LineHeight ();
  inferColFmt [0].pixWidth = MaxAlistWidths (inference_alist) + 5;
  inferColFmt [1].pixWidth = 25 * StringWidth ("X") + 5;
  SelectFont (systemFont);

  wid = 0;
  for (i = 0; i < 2; i++) {
    wid += inferColFmt [i].pixWidth;
  }

  tbl = HiddenGroup (p, -1, 0, NULL);
  SetGroupSpacing (tbl, 10, 5);
  SetGroupMargins (tbl, 5, 5);

  g0 = HiddenGroup (tbl, 15, 0, NULL);
  SetGroupSpacing (g0, 0, 3);
#ifdef WIN_MSWIN
  fnt = systemFont;
#else
  fnt = programFont;
#endif
  /*
  StaticPrompt (g0, "Category", inferColFmt [0].pixWidth, 0, fnt, 'c');
  StaticPrompt (g0, "Explanation", inferColFmt [1].pixWidth, 0, fnt, 'c');
  */

  idp->inferdoc = DocumentPanel (tbl, wid + 2, NUM_INFERENCE_LINES * hgt + 2);
  SetObjectExtra (idp->inferdoc, idp, NULL);
  SetDocCache (idp->inferdoc, NULL, NULL, NULL);
  SetDocNotify (idp->inferdoc, ChangeInferTableSelect);
  idp->numInf = 0;

  for (i = 0; i < NUM_INFERENCE_LINES; i++) {
    AppendItem (idp->inferdoc, PrintInferTable, idp, FALSE, 1,
                &inferParFmt, inferColFmt, systemFont);
  }

  cts = HiddenGroup (p, -1, 0, NULL);
  SetGroupSpacing (cts, 10, 10);
  SetGroupMargins (cts, 5, 5);

  g1 = HiddenGroup (cts, -10, 0, NULL);
  SetGroupSpacing (g1, 5, 5);

  StaticPrompt (g1, "Category", 0, popupMenuHeight, programFont, 'l');
  idp->evcat = CreateEnumPopupDialog (g1, TRUE, ChangeInferCat, evcat_alist, (UIEnum) 0, idp);

  StaticPrompt (g1, "Type", 0, popupMenuHeight, programFont, 'l');
  idp->prefix = CreateEnumPopupDialog (g1, TRUE, ChangeInferPrefix, inference_alist, (UIEnum) 0, idp);

  idp->species = CheckBox (g1, "(same species)", ChangeSameSpecies);
  SetObjectExtra (idp->species, idp, NULL);
  Hide (idp->species);

  g2 = HiddenGroup (cts, 0, 0, NULL);
  SetGroupSpacing (g2, 5, 5);

  g3 = HiddenGroup (g2, -3, 0, NULL);
  SetGroupSpacing (g3, 5, 5);

  StaticPrompt (g3, "Database", 0, dialogTextHeight, programFont, 'l');
  idp->database = CreateEnumPopupDialog (g3, TRUE, ChangeInferDatabase, accn_type_alist, (UIEnum) 0, idp);
  idp->other_db_group = HiddenGroup (g3, -4, 0, NULL);
  StaticPrompt (idp->other_db_group, ":", 0, dialogTextHeight, programFont, 'l');
  idp->db_other = DialogText (idp->other_db_group, "", 8, ChangeInferDbOther);
  SetObjectExtra (idp->db_other, idp, NULL);
  Hide (idp->other_db_group);

  StaticPrompt (g3, "Accession", 0, dialogTextHeight, programFont, 'l');
  idp->accession = DialogText (g3, "", 10, ChangeInferAccession);
  SetObjectExtra (idp->accession, idp, NULL);

  idp->inf_accn_group = g3;
  Hide (idp->inf_accn_group);

  g4 = HiddenGroup (g2, -3, 0, NULL);
  SetGroupSpacing (g4, 5, 5);

  StaticPrompt (g4, "Program", 0, dialogTextHeight, programFont, 'l');
  idp->program = CreateEnumPopupDialog (g4, TRUE, ChangeInferProgram, program_alist, (UIEnum) 0, idp);
  idp->other_pr_group = HiddenGroup (g4, -4, 0, NULL);
  StaticPrompt (idp->other_pr_group, ":", 0, dialogTextHeight, programFont, 'l');
  idp->pr_other = DialogText (idp->other_pr_group, "", 8, ChangeInferPrOther);
  SetObjectExtra (idp->pr_other, idp, NULL);
  Hide (idp->other_pr_group);

  StaticPrompt (g4, "Program Version", 0, dialogTextHeight, programFont, 'l');
  idp->version = DialogText (g4, "", 3, ChangeInferVersion);
  SetObjectExtra (idp->version, idp, NULL);

  idp->inf_prog_group = g4;
  Hide (idp->inf_prog_group);

  g5 = HiddenGroup (g2, 2, 0, NULL);
  SetGroupSpacing (g5, 5, 5);

  prompt_grp = HiddenGroup (g5, 0, 0, NULL);
  idp->inf_free_program_prompt = StaticPrompt (prompt_grp, "Program or Database", 0, dialogTextHeight, programFont, 'l');
  idp->accession_list_program_prompt = StaticPrompt (prompt_grp, "Program", 0, dialogTextHeight, programFont, 'l');
  idp->basis1 = DialogText (g5, "", 10, ChangeInferBasis1);
  SetObjectExtra (idp->basis1, idp, NULL);

  prompt_grp = HiddenGroup (g5, 0, 0, NULL);
  idp->inf_free_version_prompt = StaticPrompt (prompt_grp, "Version or Accession", 0, dialogTextHeight, programFont, 'l');
  idp->accession_list_version_prompt = StaticPrompt (prompt_grp, "Version", 0, dialogTextHeight, programFont, 'l');

  idp->basis2 = DialogText (g5, "", 10, ChangeInferBasis2);
  SetObjectExtra (idp->basis2, idp, NULL);

  idp->accession_list_prompt = StaticPrompt (g5, "Accessions", 0, dialogTextHeight, programFont, 'l');
  idp->accession_list = CreateTagListDialogEx3 (g5, 3, 2, 2,
                              accessionlist_types, accessionlist_widths,
                              accessionlist_popups, TRUE, FALSE, AccessionListDataToDialog, AccessionListDialogToData,
                              AccessionListCallbacks, idp,
                              FALSE, FALSE);

  idp->inf_free_group = g5;
  Hide (idp->inf_free_group);

  AlignObjects (ALIGN_CENTER, (HANDLE) tbl, (HANDLE) cts, NULL);

  idp->numInf = 0;
  idp->currItem = 1;
  SetDocHighlight (idp->inferdoc, 1, 1);

  return (DialoG) p;
}


typedef struct experimentdlg {
  DIALOG_MESSAGE_BLOCK
  DialoG     dlg;
} ExperimentDlgData, PNTR ExperimentDlgPtr;


Uint2 experiment_types [] = {
  TAGLIST_POPUP, TAGLIST_TEXT, TAGLIST_TEXT
};

Uint2 experiment_widths [] = {
  10, 10, 10
};

ENUM_ALIST(experiment_category_alist)
  { " ",       0 },
  { "COORDINATES", 1 },
  { "DESCRIPTION", 2 },
  { "EXISTENCE",   3 },
END_ENUM_ALIST

static EnumFieldAssocPtr experiment_popups [] = {
  experiment_category_alist, NULL
};


static Boolean StartsWithStringFollowedBySpaceAndColon (CharPtr text, CharPtr str)
{
  Int4 len;

  if (StringHasNoText (text) || StringHasNoText (str)) {
    return FALSE;
  }
  len = StringLen (str);
  if (StringNICmp (text, str, len) != 0) {
    return FALSE;
  }
  len += StringSpn (text + len, " ");
  if (text[len] == ':') {
    return TRUE;
  } else {
    return FALSE;
  }
}


static void ExperimentToDialog (DialoG d, Pointer data)
{
  ExperimentDlgPtr dlg;
  TagListPtr       tlp;
  ValNodePtr       vals, vnp;
  CharPtr          exp, fmt, doi;
  Int2             num_rows = 0;
  Int4             i, match_num;
  Int4             len;

  dlg = (ExperimentDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return;
  }
  tlp = (TagListPtr) GetObjectExtra (dlg->dlg);
  if (tlp == NULL) {
    return;
  }
  tlp->vnp = ValNodeFreeData (tlp->vnp);
  vals = (ValNodePtr) data;
  for (vnp = vals; vnp != NULL; vnp = vnp->next) {
    exp = (CharPtr) vnp->data.ptrvalue;
    if (!StringHasNoText (exp)) {
      len = 19;
      match_num = 0;
      for (i = 1; experiment_category_alist[i].name != NULL && match_num == 0; i++) {
        if (StartsWithStringFollowedBySpaceAndColon(exp, experiment_category_alist[i].name)) {
          match_num = i;
          exp = StringChr(exp, ':') + 1;
        }
      }
      len += StringLen (exp);
      doi = StringRChr (exp, '[');
      if (doi != NULL && exp[StringLen (exp) - 1] != ']') {
        doi = NULL;
      }

      fmt = (CharPtr) MemNew (sizeof (Char) + len);
      sprintf (fmt, "%d\t", match_num);
      if (doi == NULL) {
        StringCat (fmt, exp);
        StringCat (fmt, "\t");
      } else {
        StringNCat (fmt, exp, doi - exp);
        StringCat (fmt, "\t");
        StringCat (fmt, doi + 1);
        len = StringLen (fmt);
        if (fmt[len - 1] == ']') {
          fmt[len - 1] = 0;
        }
      }
      ValNodeAddPointer (&(tlp->vnp), 0, fmt);
      num_rows++;            
    }
  }

  tlp->max = MAX ((Int2) 0, (Int2) (num_rows - tlp->rows + 1));
  CorrectBarMax (tlp->bar, tlp->max);
  CorrectBarPage (tlp->bar, (Int2) (tlp->rows-1), (Int2) (tlp->rows-1));   
    
  SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
}


static Pointer ExperimentFromDialog(DialoG d)
{
  ExperimentDlgPtr dlg;
  TagListPtr       tlp;
  ValNodePtr       vals = NULL, vnp;
  CharPtr          cat_name, exp, doi, num_str, fmt;
  Int4             len, cat_num;

  dlg = (ExperimentDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return NULL;
  }
  tlp = (TagListPtr) GetObjectExtra (dlg->dlg);
  if (tlp == NULL) {
    return NULL;
  }
  
  for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
    num_str = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
    exp = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 1);
    doi = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 2);
    cat_name = NULL;
    if (!StringHasNoText (num_str) && (cat_num = atoi(num_str)) > 0) {
      cat_name = experiment_category_alist[cat_num].name;
    }
    num_str = MemFree (num_str);
    len = StringLen (exp) + 1;
    if (cat_name != NULL) {
      len += StringLen (cat_name) + 1;
    }
    if (StringHasNoText (doi)) {
      doi = MemFree (doi);
    }
    if (doi != NULL) {
      len += StringLen (doi) + 2;
    }
    fmt = (CharPtr) MemNew (sizeof (Char) * len);
    sprintf (fmt, "%s%s%s%s%s%s",
             cat_name == NULL ? "" : cat_name,
             cat_name == NULL ? "" : ":",
             exp == NULL ? "" : exp,
             doi == NULL ? "" : "[",
             doi == NULL ? "" : doi,
             doi == NULL ? "" : "]");
    ValNodeAddPointer (&vals, 0, fmt);
    exp = MemFree (exp);
    doi = MemFree (doi);
  }
  return (Pointer) vals;
}


extern DialoG CreateExperimentDialog (GrouP prnt)
{
  GrouP            p, g;
  ExperimentDlgPtr dlg;
  TagListPtr       tlp;
  PrompT           p1, p2, p3;

  dlg = (ExperimentDlgPtr) MemNew (sizeof (ExperimentDlgData));
  if (dlg == NULL) return NULL;

  p = HiddenGroup (prnt, -1, 0, NULL);
  SetGroupSpacing (p, 10, 10);

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = ExperimentToDialog;
  dlg->fromdialog = ExperimentFromDialog;

  g = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  SelectFont(systemFont);
  p1 = StaticPrompt (g, "Category (optional)", 0, 0, systemFont, 'c');
  p2 = StaticPrompt (g, "Experiment", 0, 0, systemFont, 'c');
  p3 = StaticPrompt (g, "PMID or doi (optional)", 0, 0, systemFont, 'c');
  dlg->dlg = CreateTagListDialogEx3 (p, 3, 3, 2,
                              experiment_types, experiment_widths,
                              experiment_popups, TRUE, FALSE, NULL, NULL,
                              NULL, NULL,
                              FALSE, FALSE);
  tlp = GetObjectExtra (dlg->dlg);
  AlignObjects (ALIGN_JUSTIFY, (HANDLE) p1, (HANDLE) tlp->control[0], NULL);
  AlignObjects (ALIGN_JUSTIFY, (HANDLE) p2, (HANDLE) tlp->control[1], NULL);
  AlignObjects (ALIGN_JUSTIFY, (HANDLE) p3, (HANDLE) tlp->control[2], NULL);

  return (DialoG) p;
}


extern void ExperimentDialogToGbquals (SeqFeatPtr sfp, DialoG d)

{
  GBQualPtr   gbq, gbqlast = NULL;
  ValNodePtr  head = NULL, vnp;
  CharPtr     str;

  if (sfp == NULL) return;
  head = DialogToPointer (d);
  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    gbqlast = gbq;
  }
  for (vnp = head; vnp != NULL; vnp = vnp->next) {
    str = (CharPtr) vnp->data.ptrvalue;
    if (StringHasNoText (str)) continue;
    gbq = GBQualNew ();
    if (gbq == NULL) continue;
    gbq->qual = StringSave ("experiment");
    gbq->val = StringSave (str);
    if (gbqlast == NULL) {
      sfp->qual = gbq;
    } else {
      gbqlast->next = gbq;
    }
    gbqlast = gbq;
  }
  ValNodeFreeData (head);
}


extern void GBQualsToExperimentDialog (SeqFeatPtr sfp, DialoG d)
{
  ValNodePtr list = NULL;
  GBQualPtr   gbq;

  if (sfp != NULL) {
    for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
      if (StringICmp (gbq->qual, "experiment") == 0) {
        ValNodeAddPointer (&list, 0, gbq->val);
      }
    }
  }
  PointerToDialog (d, list);
  list = ValNodeFree (list);
}


/* ExistingText handling dialog and structures */
typedef struct existingtextdlg 
{
  GrouP pre_app_grp;
  GrouP delim_grp;
} ExistingTextDlgData, PNTR ExistingTextDlgPtr;

static void ChangePreAppIgnoreChoice (GrouP g)
{
  ExistingTextDlgPtr etdp;
  Int4               handle_choice;
  
  etdp = (ExistingTextDlgPtr) GetObjectExtra (g);
  if (etdp == NULL)
  {
    return;
  }
  
  handle_choice = GetValue (etdp->pre_app_grp);
  if (handle_choice == 1 || handle_choice == 2)
  {
    Enable (etdp->delim_grp);
  }
  else
  {
    Disable (etdp->delim_grp);
  }
}

extern ExistingTextPtr GetExistingTextHandlerInfo (Int4 num_found, Boolean non_text)
{
  WindoW                w;
  GrouP                 h, c;
  ExistingTextDlgData   etdd;
  ButtoN                b;
  ModalAcceptCancelData acd;
  ExistingTextPtr       etp;
  MsgAnswer             ans;
  PrompT                ppt;
  Int4                  handle_choice;

  if (num_found <= 0)
  {
    return NULL;
  }
  
  ans = Message (MSG_YNC, "%d affected fields already contain a value.  Do you wish to overwrite existing text?", num_found);
  if (ans == ANS_CANCEL)
  {
    etp = (ExistingTextPtr) MemNew (sizeof (ExistingTextData));
    etp->existing_text_choice = eExistingTextChoiceCancel;
    return etp;
  }
  else if (ans == ANS_YES)
  {
    etp = (ExistingTextPtr) MemNew (sizeof (ExistingTextData));
    etp->existing_text_choice = eExistingTextChoiceReplaceOld;
    return etp;
  }
    
  w = MovableModalWindow(-20, -13, -10, -10, "How to Add New Text", NULL);
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  etdd.pre_app_grp = HiddenGroup (h, 0, 3, ChangePreAppIgnoreChoice);
  SetGroupSpacing (etdd.pre_app_grp, 10, 10);
  RadioButton (etdd.pre_app_grp, "Append");
  RadioButton (etdd.pre_app_grp, "Prefix");
  RadioButton (etdd.pre_app_grp, "Ignore new text");
  SetValue (etdd.pre_app_grp, 1);
  SetObjectExtra (etdd.pre_app_grp, &etdd, NULL);
  
  ppt = StaticPrompt (h, "Separate new text and old text with", 
                      0, dialogTextHeight, programFont, 'c');
  etdd.delim_grp = HiddenGroup (h, 0, 4, NULL);
  SetGroupSpacing (etdd.delim_grp, 10, 10);
  RadioButton (etdd.delim_grp, "Semicolon");
  RadioButton (etdd.delim_grp, "Space");
  RadioButton (etdd.delim_grp, "Colon");
  RadioButton (etdd.delim_grp, "Do not separate");
  SetValue (etdd.delim_grp, 1);
  
  c = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (c, 10, 10);
  b = PushButton (c, "Accept", ModalAcceptButton);
  SetObjectExtra (b, &acd, NULL);
  b = PushButton (c, "Cancel", ModalCancelButton);
  SetObjectExtra (b, &acd, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) etdd.pre_app_grp,
                              (HANDLE) ppt, 
                              (HANDLE) etdd.delim_grp, 
                              (HANDLE) c, 
                              NULL);
  Show (w);
  Select (w);
  acd.accepted = FALSE;
  acd.cancelled = FALSE;
  while (!acd.accepted && ! acd.cancelled)
  {
    ProcessExternalEvent ();
    Update ();
  }
  ProcessAnEvent ();
  etp = (ExistingTextPtr) MemNew (sizeof (ExistingTextData));
  if (acd.cancelled)
  {
    etp->existing_text_choice = eExistingTextChoiceCancel;
  }
  else
  {
    handle_choice = GetValue (etdd.pre_app_grp);
    if (handle_choice == 1)
    {
      switch (GetValue (etdd.delim_grp))
      {
        case 1:
          etp->existing_text_choice = eExistingTextChoiceAppendSemi;
          break;
        case 2:
          etp->existing_text_choice = eExistingTextChoiceAppendSpace;
          break;
        case 3:
          etp->existing_text_choice = eExistingTextChoiceAppendColon;
          break;
        case 4:
          etp->existing_text_choice = eExistingTextChoiceAppendNone;
          break;
      }
    }
    else if (handle_choice == 2)
    {
      switch (GetValue (etdd.delim_grp))
      {
        case 1:
          etp->existing_text_choice = eExistingTextChoicePrefixSemi;
          break;
        case 2:
          etp->existing_text_choice = eExistingTextChoicePrefixSpace;
          break;
        case 3:
          etp->existing_text_choice = eExistingTextChoicePrefixColon;
          break;
        case 4:
          etp->existing_text_choice = eExistingTextChoicePrefixNone;
          break;
      }
    }
    else
    {
      etp->existing_text_choice = eExistingTextChoiceLeaveOld;
    }
  }
  Remove (w);
  return etp;
}

extern CharPtr HandleExistingText (CharPtr existing_text, CharPtr new_text, ExistingTextPtr etp)
{
  CharPtr rstring = NULL;
  Int4    len;
  
  if (StringHasNoText (existing_text) || etp == NULL)
  {
    MemFree (existing_text);
    return new_text;
  }
  switch (etp->existing_text_choice)
  {
    case eExistingTextChoiceReplaceOld:
      /* replace current text with new text */
      MemFree (existing_text);
      rstring = new_text;
      break;
    case eExistingTextChoiceLeaveOld:
      /* do not change current text */
      MemFree (new_text);
      rstring = existing_text;
      break;
    case eExistingTextChoiceAppendSemi:
      /* Append new text to current text, separated by semicolon */
      len = StringLen (new_text) + StringLen (existing_text) + 4;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, existing_text);
        StringCat (rstring, "; ");
        StringCat (rstring, new_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoiceAppendSpace:
      /* Append new text to current text, separated by space */
      len = StringLen (new_text) + StringLen (existing_text) + 3;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, existing_text);
        StringCat (rstring, " ");
        StringCat (rstring, new_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoiceAppendColon:
      /* Append new text to current text, separated by colon */
      len = StringLen (new_text) + StringLen (existing_text) + 4;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, existing_text);
        StringCat (rstring, ": ");
        StringCat (rstring, new_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoiceAppendNone:
      /* Append new text to current text, no delimiter */
      len = StringLen (new_text) + StringLen (existing_text) + 1;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, existing_text);
        StringCat (rstring, new_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoicePrefixSemi:
      /* Prepend new text to current text, separated by semicolon */
      len = StringLen (new_text) + StringLen (existing_text) + 4;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, new_text);
        StringCat (rstring, "; ");
        StringCat (rstring, existing_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoicePrefixSpace:
      /* Prepend new text to current text, separated by space */
      len = StringLen (new_text) + StringLen (existing_text) + 3;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, new_text);
        StringCat (rstring, " ");
        StringCat (rstring, existing_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoicePrefixColon:
      /* Prepend new text to current text, separated by colon */
      len = StringLen (new_text) + StringLen (existing_text) + 4;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, new_text);
        StringCat (rstring, ": ");
        StringCat (rstring, existing_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;
    case eExistingTextChoicePrefixNone:
      /* prefix current text with new text */
      len = StringLen (new_text) + StringLen (existing_text) + 1;
      rstring = MemNew (len);
      if (rstring != NULL) {
        StringCpy (rstring, new_text);
        StringCat (rstring, existing_text);
        MemFree (new_text);
        MemFree (existing_text);
      }
      break;    
  }
  return rstring;
}


/* Move EditApply data and dialog here */
extern EditApplyPtr EditApplyFree (EditApplyPtr eap)
{
  if (eap != NULL)
  {
    eap->find_txt = MemFree (eap->find_txt);
    eap->repl_txt = MemFree (eap->repl_txt);
    eap->apply_txt = MemFree (eap->apply_txt);
    eap = MemFree (eap);
  }
  return eap;
}


extern EditApplyPtr EditApplyNew (void)
{
  EditApplyPtr eap;

  eap = (EditApplyPtr) MemNew (sizeof (EditApplyData));
  eap->find_location = EditApplyFindLocation_anywhere;
  return eap;
}


typedef struct EditApplydlg
{
  DIALOG_MESSAGE_BLOCK
  TexT           find_txt;
  TexT           repl_txt;
  TexT           apply_txt;

  DialoG         find_dlg;
  DialoG         repl_dlg;
  DialoG         apply_dlg;

  Int4           action_choice;
  GrouP          location_choice;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} EditApplyDlgData, PNTR EditApplyDlgPtr;

static void ResetEditApplyDlg (EditApplyDlgPtr dlg)
{
  if (dlg != NULL)
  {
    if (dlg->find_txt != NULL)
    {
      SetTitle (dlg->find_txt, "");
    }
    if (dlg->repl_txt != NULL)
    {
      SetTitle (dlg->repl_txt, "");
    }
    if (dlg->apply_txt != NULL)
    {
      SetTitle (dlg->apply_txt, "");
    }
    if (dlg->location_choice != NULL) {
      SetValue (dlg->location_choice, EditApplyFindLocation_anywhere);
    }

    PointerToDialog (dlg->find_dlg, NULL);
    PointerToDialog (dlg->repl_dlg, NULL);
    PointerToDialog (dlg->apply_dlg, NULL);

  }
}

static void EditApplyDialogChangeText (TexT t)
{
  EditApplyDlgPtr dlg;

  dlg = (EditApplyDlgPtr) GetObjectExtra (t);
  if (dlg != NULL && dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }
}

static void EditApplyToDialog (DialoG d, Pointer userdata)
{
  EditApplyDlgPtr dlg;
  EditApplyPtr    data;
  ValNode         vn;
  
  dlg = (EditApplyDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetEditApplyDlg (dlg);
  data = (EditApplyPtr) userdata;

  vn.next = NULL;
  vn.choice = 0;

  if (data != NULL)
  {
    if (!StringHasNoText (data->find_txt))
    {
      if (dlg->find_txt != NULL)
      {
        SetTitle (dlg->find_txt, data->find_txt);
      }
      else if (dlg->find_dlg != NULL)
      {
        vn.data.ptrvalue = data->find_txt;
        PointerToDialog (dlg->find_dlg, &vn);
      }
    }

    if (!StringHasNoText (data->repl_txt))
    {
      if (dlg->repl_txt != NULL) 
      {
        SetTitle (dlg->repl_txt, data->repl_txt);
      }
      else if (dlg->repl_dlg != NULL)
      {
        vn.data.ptrvalue = data->repl_txt;
        PointerToDialog (dlg->repl_dlg, &vn);
      }
    }

    if (!StringHasNoText (data->apply_txt))
    {
      if (dlg->apply_txt != NULL)
      {
        SetTitle (dlg->apply_txt, data->apply_txt);
      }
      else if (dlg->apply_dlg != NULL)
      {
        vn.data.ptrvalue = data->apply_txt;
        PointerToDialog (dlg->apply_dlg, &vn);
      }
    }

    if (dlg->location_choice != NULL) {
      SetValue (dlg->location_choice, data->find_location);
    }
  }
}

static Pointer DialogToEditApply (DialoG d)
{
  EditApplyDlgPtr dlg;
  EditApplyPtr    data;
  ValNodePtr      vnp;
  
  dlg = (EditApplyDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  data = (EditApplyPtr) MemNew (sizeof (EditApplyData));
  if (data != NULL)
  {
    if (dlg->find_txt != NULL)
    {
      data->find_txt = JustSaveStringFromText (dlg->find_txt);
    }
    else if (dlg->find_dlg != NULL)
    {
      vnp = (ValNodePtr) DialogToPointer (dlg->find_dlg);
      if (vnp != NULL)
      {
        data->find_txt = StringSave (vnp->data.ptrvalue);
      }
      vnp = ValNodeFreeData (vnp);
    }

    if (dlg->repl_txt != NULL)
    {
      data->repl_txt = JustSaveStringFromText (dlg->repl_txt);
    }
    else if (dlg->repl_dlg != NULL)
    {
      vnp = (ValNodePtr) DialogToPointer (dlg->repl_dlg);
      if (vnp != NULL)
      {
        data->repl_txt = StringSave (vnp->data.ptrvalue);
      }
      vnp = ValNodeFreeData (vnp);
    }

    if (dlg->apply_txt != NULL)
    {
      data->apply_txt = JustSaveStringFromText (dlg->apply_txt);
    }
    else if (dlg->apply_dlg != NULL)
    {
      vnp = (ValNodePtr) DialogToPointer (dlg->apply_dlg);
      if (vnp != NULL)
      {
        data->apply_txt = StringSave (vnp->data.ptrvalue);
      }
      vnp = ValNodeFreeData (vnp);
    }

    if (dlg->location_choice != NULL) {
      data->find_location = (EditApplyFindLocation) GetValue (dlg->location_choice);
    } else {
      data->find_location = EditApplyFindLocation_anywhere;
    }
  }
  return data;
}


static void EditApplyMessage (DialoG d, Int2 mssg)

{
  EditApplyDlgPtr  dlg;

  dlg = (EditApplyDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) 
    {
      case VIB_MSG_INIT :
        /* reset list */
        ResetEditApplyDlg (dlg);
        break;
      case VIB_MSG_ENTER :
        if (dlg->find_txt != NULL)
        {
          Select (dlg->find_txt);
        }
        else if (dlg->apply_txt != NULL)
        {
          Select (dlg->apply_txt);
        }
        else if (dlg->find_dlg != NULL)
        {
          Select (dlg->find_dlg);
        }
        else if (dlg->apply_dlg != NULL)
        {
          Select (dlg->apply_dlg);
        }
        break;
      case NUM_VIB_MSG + 1 :
        SafeSetTitle (dlg->apply_txt, "");
        SafeSetTitle (dlg->find_txt, "");
        SafeSetTitle (dlg->repl_txt, "");
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestEditApply (DialoG d)
{
  EditApplyDlgPtr dlg;
  ValNodePtr      total_err_list = NULL, err_list;
  
  dlg = (EditApplyDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return FALSE;
  }

  if (dlg->action_choice == eEditApplyChoice_Apply)
  {
    if (dlg->apply_dlg == NULL) 
    {
      if (TextHasNoText (dlg->apply_txt))
      {
        ValNodeAddPointer (&total_err_list, 0, StringSave ("apply text"));
      }
    }
    else
    {
      total_err_list = TestDialog (dlg->apply_dlg);
    }
  }
  else if (dlg->action_choice == eEditApplyChoice_Edit)
  {
    if (dlg->find_dlg == NULL)
    {
      if (TextHasNoText (dlg->find_txt))
      {
        ValNodeAddPointer (&total_err_list, 0, StringSave ("find text"));
      }
    }
    else 
    {
      total_err_list = TestDialog (dlg->find_dlg);
      err_list = TestDialog (dlg->repl_dlg);
      ValNodeLink (&total_err_list, err_list);
    }
  }
  return total_err_list;
}


static void EditApplyDialogCopy (ButtoN b)
{
  EditApplyDlgPtr dlg;
  CharPtr         str = NULL;

  dlg = (EditApplyDlgPtr) GetObjectExtra (b);
  if (dlg == NULL)
  {
    return;
  }
  str = JustSaveStringFromText (dlg->find_txt);
  SetTitle (dlg->repl_txt, str);
  str = MemFree (str);
}

extern DialoG EditApplyDialog 
(GrouP                    h,
 Int4                     action_choice, 
 CharPtr                  apply_label,
 ValNodePtr               choice_list,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  EditApplyDlgPtr dlg;
  GrouP           p, p1 = NULL;
  ButtoN          b;
  ValNodePtr      cpy;
  
  dlg = (EditApplyDlgPtr) MemNew (sizeof (EditApplyDlgData));
  if (dlg == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  SetGroupSpacing (p, 10, 10);

  dlg->dialog = (DialoG) p;
  dlg->todialog = EditApplyToDialog;
  dlg->fromdialog = DialogToEditApply;
  dlg->dialogmessage = EditApplyMessage;
  dlg->testdialog = TestEditApply;
  dlg->action_choice = action_choice;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  if (choice_list == NULL)
  {
    p1 = HiddenGroup (p, 3, 0, NULL);
    SetGroupSpacing (p1, 10, 10);

    if (action_choice == eEditApplyChoice_Apply)
    {
      StaticPrompt (p1, apply_label, 0, dialogTextHeight, systemFont, 'r');
      dlg->apply_txt = DialogText (p1, "", 20, EditApplyDialogChangeText);
      SetObjectExtra (dlg->apply_txt, dlg, NULL);
      dlg->location_choice = NULL;
    }
    else if (action_choice == eEditApplyChoice_Edit)
    {
      StaticPrompt (p1, "Find", 0, dialogTextHeight, systemFont, 'r');
      dlg->find_txt = DialogText (p1, "", 18, EditApplyDialogChangeText);
      SetObjectExtra (dlg->find_txt, dlg, NULL);
      b = PushButton (p1, "Copy", EditApplyDialogCopy);
      SetObjectExtra (b, dlg, NULL);
      Hide (b);
      StaticPrompt (p1, "Replace", 0, dialogTextHeight, systemFont, 'r');
      dlg->repl_txt = DialogText (p1, "", 18, EditApplyDialogChangeText);
      SetObjectExtra (dlg->repl_txt, dlg, NULL);
      b = PushButton (p1, "Copy", EditApplyDialogCopy);
      SetObjectExtra (b, dlg, NULL);

      dlg->location_choice = HiddenGroup (p, 3, 0, NULL);
      RadioButton (dlg->location_choice, "Anywhere in field");
      RadioButton (dlg->location_choice, "At the beginning of the field");
      RadioButton (dlg->location_choice, "At the end of the field");
      SetValue (dlg->location_choice, EditApplyFindLocation_anywhere);
    }
  }
  else
  {
    if (action_choice == eEditApplyChoice_Apply)
    {
      p1 = HiddenGroup (p, 1, 0, NULL);
      SetGroupSpacing (p1, 10, 10);
      if (!StringHasNoText (apply_label))
      {
        StaticPrompt (p1, apply_label, 0, dialogTextHeight, systemFont, 'r');
      }
      cpy = ValNodeDupStringList (choice_list);
      dlg->apply_dlg = ValNodeSelectionDialog (p1, cpy, 6,
                                               ValNodeStringName,
                                               ValNodeSimpleDataFree,
                                               ValNodeStringCopy,
                                               ValNodeStringMatch,
                                               apply_label,
                                               dlg->change_notify, dlg->change_userdata, FALSE);
    }
    else if (action_choice == eEditApplyChoice_Edit)
    {
      p1 = HiddenGroup (p, 2, 0, NULL);
      SetGroupSpacing (p1, 10, 10);
      StaticPrompt (p1, "From", 0, dialogTextHeight, systemFont, 'r');
      StaticPrompt (p1, "To", 0, dialogTextHeight, systemFont, 'r');

      cpy = ValNodeDupStringList (choice_list);
      dlg->find_dlg = ValNodeSelectionDialog (p1, cpy, 6,
                                               ValNodeStringName,
                                               ValNodeSimpleDataFree,
                                               ValNodeStringCopy,
                                               ValNodeStringMatch,
                                               "Original",
                                               dlg->change_notify, dlg->change_userdata, FALSE);
      cpy = ValNodeDupStringList (choice_list);
      dlg->repl_dlg = ValNodeSelectionDialog (p1, cpy, 6,
                                               ValNodeStringName,
                                               ValNodeSimpleDataFree,
                                               ValNodeStringCopy,
                                               ValNodeStringMatch,
                                               "New",
                                               dlg->change_notify, dlg->change_userdata, FALSE);

    }
    dlg->location_choice = NULL;
  }
  AlignObjects (ALIGN_CENTER, (HANDLE) p1, (HANDLE) dlg->location_choice, NULL);

  return (DialoG) p;
}


/* Global Inference Editor Dialog */

extern InferenceParsePtr ParseInferenceText (CharPtr inference)
{
  CharPtr cp1, cp2, cp3;
  Int4    len;
  InferenceParsePtr ipp;

  if (StringHasNoText (inference))
  {
    return NULL;
  }

  cp1 = StringChr (inference, ':');
  if (cp1 == NULL)  
  {
    return NULL;
  }
  cp2 = StringChr (cp1 + 1, ':');
  if (cp2 == NULL)
  {
    return NULL;
  }
  cp3 = StringChr (cp2 + 1, ':');
  if (cp3 == NULL) 
  {
    return NULL;
  }

  ipp = (InferenceParsePtr) MemNew (sizeof (InferenceParseData));
  ipp->second_field = StringSave (cp3 + 1);

  len = cp3 - cp2;
  ipp->first_field = (CharPtr) MemNew (sizeof (Char) * len);
  StringNCpy (ipp->first_field, cp2 + 1, len - 1);
  ipp->first_field[len - 1] = 0;

  /* look for same species */
  cp3 = StringISearch (inference, "(same species)");
  if (cp3 != NULL && cp3 < cp2)
  {
    ipp->same_species = TRUE;
    cp2 = cp3;
  } 
  else
  {
    ipp->same_species = FALSE;
  }

  /* type */
  len = cp2 - cp1;
  ipp->type = (CharPtr) MemNew (sizeof (Char) * len);
  StringNCpy (ipp->type, cp1 + 1, len - 1);
  ipp->type[len - 1] = 0;

  len = cp1 - inference + 1;
  ipp->category = (CharPtr) MemNew (sizeof (Char) * len);
  StringNCpy (ipp->category, inference, len - 1);
  ipp->category[len - 1] = 0;
  TrimSpacesAroundString (ipp->category);
  TrimSpacesAroundString (ipp->type);
  TrimSpacesAroundString (ipp->first_field);
  TrimSpacesAroundString (ipp->second_field);

  return ipp;  
}


extern CharPtr InferenceTextFromStruct (InferenceParsePtr ipp)
{
  Int4 len;
  CharPtr inference = NULL;
  CharPtr same_sp = " (same species)";

  if (ipp == NULL) return NULL;

  len = StringLen (ipp->category) + + StringLen (ipp->type) + StringLen (ipp->first_field) + StringLen (ipp->second_field)
        + 4;
  if (ipp->same_species)
  {
    len += StringLen (same_sp);
  }

  inference = (CharPtr) MemNew (sizeof (Char) * len);
  sprintf (inference, "%s:%s%s:%s:%s", ipp->category == NULL ? "" : ipp->category,
                                    ipp->type == NULL ? "" : ipp->type,
                                    ipp->same_species ? same_sp : "",
                                    ipp->first_field == NULL ? "" : ipp->first_field,
                                    ipp->second_field == NULL ? "" : ipp->second_field);
  return inference;
}


typedef enum {
  eInferenceCategorySimilarTo = 0,
  eInferenceCategoryProgram,
  eInferenceCategoryAbInitio,
  eNumInferenceCategories } EInferenceCategoryType;


static Int4 GetCategoryTypeFromNum (Int4 num)
{
  if (num > 0 && num < 8) 
  {
    return eInferenceCategorySimilarTo;
  }
  else if (num > 7 && num < 11)
  {
    return eInferenceCategoryProgram;
  }
  else if (num == 11)
  {
    return eInferenceCategoryAbInitio;
  }
  else
  {
    return -1;
  }
}


static Int4 GetCategoryNumFromName (CharPtr category)
{
  Int4 i;

  if (StringHasNoText (category)) 
  {
    return -1;
  }

  for (i = 0; inference_alist[i].name != NULL; i++)
  {
    if (StringICmp (inference_alist[i].name, category) == 0)
    {
      return i;
    }
  }
  return -1;
}


extern InferenceFieldEditPtr InferenceFieldEditFree (InferenceFieldEditPtr ifep)
{
  if (ifep != NULL)
  {
    ifep->edit_apply = EditApplyFree (ifep->edit_apply);
    ifep = MemFree (ifep);
  }
  return ifep;
}

extern InferenceEditPtr InferenceEditFree (InferenceEditPtr iep)
{
  if (iep != NULL)
  {
    iep->field_edit = InferenceFieldEditFree (iep->field_edit);
    iep = MemFree (iep);
  }
  return iep;
}

typedef struct inferencefieldeditdialog
{
  DIALOG_MESSAGE_BLOCK

  PopuP  field_type;
  PopuP  field_list[eNumInferenceCategories];
  DialoG field_editors[eNumInferenceCategories * 2];
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;

} InferenceFieldEditDialogData, PNTR InferenceFieldEditDialogPtr;


static Pointer InferenceFieldEditDataFromDialog (DialoG d)
{
  InferenceFieldEditDialogPtr dlg;
  UIEnum                      val;
  Int4                        i, j;
  InferenceFieldEditPtr       data = NULL;

  dlg = (InferenceFieldEditDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;

  if (GetEnumPopup (dlg->field_type, inference_alist, &val)
      && val > 0
      && (i = GetCategoryTypeFromNum (val)) > -1
      && (j = GetValue (dlg->field_list[i])) > 0
      && j < 3)
  {
    data = (InferenceFieldEditPtr) MemNew (sizeof (InferenceFieldEditData));
    data->field_type = inferencePrefix[val];
    data->field_choice = j - 1;
    data->edit_apply = DialogToPointer (dlg->field_editors[2 * i + j - 1]);
  }
  return data;
}


static void ChangeInferenceFieldChoice (PopuP p)
{
  InferenceFieldEditDialogPtr dlg;
  UIEnum                      val;
  Int4                        i, j;

  dlg = (InferenceFieldEditDialogPtr) GetObjectExtra (p);
  if (dlg == NULL) return;

  for (i = eInferenceCategorySimilarTo;
       i <= eInferenceCategoryAbInitio;
       i++)
  {
    Hide (dlg->field_list[i]);
    Hide (dlg->field_editors[2 * i]);
    Hide (dlg->field_editors[2 * i + 1]);
  }

  if (GetEnumPopup (dlg->field_type, inference_alist, &val)
      && val > 0
      && (i = GetCategoryTypeFromNum (val)) > -1)
  {
    Show (dlg->field_list[i]);
    j = GetValue (dlg->field_list[i]);
    if (j > 0 && j < 3)
    {
      Show (dlg->field_editors[2 * i + j - 1]);
    }
  }

  if (dlg->change_notify != NULL) 
  {
    (dlg->change_notify)(dlg->change_userdata);
  }
}

static ValNodePtr MakeValNodeListFromEnum ( EnumFieldAssocPtr al)
{
  EnumFieldAssocPtr efap;
  ValNodePtr        list;

  efap = al;
  list = NULL;
  while (efap->name != NULL)
  {
    ValNodeAddStr (&list, efap->value, StringSave (efap->name));
    efap ++;
  }
  return list;
}


static DialoG 
CreateInferenceFieldEditApplyDialog 
(GrouP                h,
 Int4                 action_choice,
 Nlm_ChangeNotifyProc change_notify,
 Pointer              change_userdata)
{
  InferenceFieldEditDialogPtr dlg;
  GrouP                       p, k;
  Int4                        i;
  ValNodePtr                  choice_list;

  dlg = (InferenceFieldEditDialogPtr) MemNew (sizeof (InferenceFieldEditDialogData));
  if (dlg == NULL)
  {
    return NULL;
  }
  p = HiddenGroup (h, 3, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  SetGroupSpacing (p, 10, 10);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = NULL;
  dlg->fromdialog = InferenceFieldEditDataFromDialog;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;
  
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  StaticPrompt (p, "Type", 0, popupMenuHeight, programFont, 'c');
  StaticPrompt (p, "Field", 0, popupMenuHeight, programFont, 'c');
  if (action_choice == eEditApplyChoice_Apply) 
  {
    StaticPrompt (p, "New Value", 0, popupMenuHeight, programFont, 'c');
  }
  else
  {
    StaticPrompt (p, "Convert", 0, popupMenuHeight, programFont, 'c');
  }

  dlg->field_type = PopupList (p, TRUE, ChangeInferenceFieldChoice);
  SetObjectExtra (dlg->field_type, dlg, NULL);
  InitEnumPopup (dlg->field_type, inference_alist, NULL);


  k = HiddenGroup (p, 0, 0, NULL);
  dlg->field_list[eInferenceCategorySimilarTo] = PopupList (k, TRUE, ChangeInferenceFieldChoice);
  SetObjectExtra (dlg->field_list[eInferenceCategorySimilarTo], dlg, NULL);
  PopupItem (dlg->field_list[eInferenceCategorySimilarTo], "Database");
  PopupItem (dlg->field_list[eInferenceCategorySimilarTo], "Accession");
  
  dlg->field_list[eInferenceCategoryProgram] = PopupList (k, TRUE, ChangeInferenceFieldChoice);
  SetObjectExtra (dlg->field_list[eInferenceCategoryProgram], dlg, NULL);
  PopupItem (dlg->field_list[eInferenceCategoryProgram], "Program or Database");
  PopupItem (dlg->field_list[eInferenceCategoryProgram], "Version or Accession");
  
  dlg->field_list[eInferenceCategoryAbInitio] = PopupList (k, TRUE, ChangeInferenceFieldChoice);
  SetObjectExtra (dlg->field_list[eInferenceCategoryAbInitio], dlg, NULL);
  PopupItem (dlg->field_list[eInferenceCategoryAbInitio], "Program");
  PopupItem (dlg->field_list[eInferenceCategoryAbInitio], "Program Version");

  k = HiddenGroup (p, 0, 0, NULL);
  i = 0;
  choice_list = MakeValNodeListFromEnum (accn_type_alist);
  dlg->field_editors[i++] = EditApplyDialog (k, action_choice, "", choice_list, change_notify, change_userdata);
  dlg->field_editors[i++] = EditApplyDialog (k, action_choice, "", NULL, change_notify, change_userdata);
  dlg->field_editors[i++] = EditApplyDialog (k, action_choice, "", NULL, change_notify, change_userdata);
  dlg->field_editors[i++] = EditApplyDialog (k, action_choice, "", NULL, change_notify, change_userdata);
  choice_list = MakeValNodeListFromEnum (program_alist);
  dlg->field_editors[i++] = EditApplyDialog (k, action_choice, "", choice_list, change_notify, change_userdata);
  dlg->field_editors[i++] = EditApplyDialog (k, action_choice, "", NULL, change_notify, change_userdata);

  ChangeInferenceFieldChoice (dlg->field_type);
  return (DialoG) p;
}


typedef struct inferenceeditdialog {
  DIALOG_MESSAGE_BLOCK
  PopuP action;
  GrouP action_pages[eNumInferenceEditActions];
  PopuP category_from;
  PopuP category_to;
  PopuP type_from;
  PopuP type_to;
  
  DialoG apply_field;
  DialoG edit_field;

  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_userdata;

} InferenceEditDialogData, PNTR InferenceEditDialogPtr;


static Pointer InferenceEditDataFromDialog (DialoG d)
{
  InferenceEditDialogPtr dlg;
  InferenceEditPtr       data;
  Int4                   i;

  dlg = (InferenceEditDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;

  data = (InferenceEditPtr) MemNew (sizeof (InferenceEditData));
  if (data == NULL) return NULL;

  data->action = (EInferenceEditAction) (GetValue (dlg->action) - 1);

  switch (data->action)
  {
    case eInferenceRemove:
      /* no other data needed */
      break;
    case eInferenceEditCategory:
      i = GetValue (dlg->category_from);
      if (i <= 1) 
      {
        data->category_from = NULL;
      }
      else
      {
        data->category_from = evcat_alist[i - 2].name;
      }
      i = GetValue (dlg->category_to);
      if (i < 1)
      {
        data->category_to = NULL;
      }
      else
      {
        data->category_to = evcat_alist[i - 1].name;
      }
      break;
    case eInferenceEditType:
      i = GetValue (dlg->type_from);
      if (i <= 1) 
      {
        data->type_from = NULL;
      }
      else
      {
        data->type_from = inference_alist[i - 2].name;
      }
      i = GetValue (dlg->type_to);
      if (i < 1)
      {
        data->type_to = NULL;
      }
      else
      {
        data->type_to = inference_alist[i - 1].name;
      }
      break;
    case eInferenceApplyTypeFields:
     data->field_edit = DialogToPointer (dlg->apply_field);
     break;
    case eInferenceEditTypeFields:
     data->field_edit = DialogToPointer (dlg->edit_field);
      break;
    default:
      break;
  }

  return data;
}


static void ChangeInferenceEditAction (PopuP p)
{
  InferenceEditDialogPtr dlg;
  Int4                   i;

  dlg = (InferenceEditDialogPtr) GetObjectExtra (p);
  if (dlg == NULL) return;

  ResetClip();
  for (i = 0; i < eNumInferenceEditActions; i++)
  {
    Hide (dlg->action_pages[i]);
  }

  i = GetValue (dlg->action);
  if (i > 0 && i <= eNumInferenceEditActions)
  {
    Show (dlg->action_pages[i - 1]);
  }

  if (dlg->change_notify != NULL) 
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static void ChangeInferenceCategoryChoice (PopuP p)
{
  InferenceEditDialogPtr dlg;

  dlg = (InferenceEditDialogPtr) GetObjectExtra (p);
  if (dlg == NULL) return;

  if (dlg->change_notify != NULL) 
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static ValNodePtr TestInferenceEditDialog (DialoG d)
{
  ValNodePtr             err_list = NULL;
  InferenceEditPtr       iep;

  iep = DialogToPointer (d);
  if (iep == NULL)
  {
    ValNodeAddPointer (&err_list, 0, "no values");
  }
  else
  {
    switch (iep->action)
    {
      case eInferenceRemove:
        /* nothing to check */
        break;
      case eInferenceEditCategory:
        if (StringHasNoText (iep->category_from))
        {
          ValNodeAddPointer (&err_list, 0, "missing category from");
        }
        if (StringHasNoText (iep->category_to))
        {
          ValNodeAddPointer (&err_list, 0, "missing category to");
        }
        break;
      case eInferenceEditType:
        if (StringHasNoText (iep->type_from))
        {
          ValNodeAddPointer (&err_list, 0, "missing type from");
        }
        if (StringHasNoText (iep->type_to))
        {
          ValNodeAddPointer (&err_list, 0, "missing type to");
        }
        break;
      case eInferenceApplyTypeFields:
      case eInferenceEditTypeFields:
        if (iep->field_edit == NULL)
        {
          ValNodeAddPointer (&err_list, 0, "missing edit data");
        }
        break;
      default:
        break;
    }
  }
  iep = InferenceEditFree (iep);

  return err_list;
}


extern DialoG CreateInferenceEditDialog 
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  InferenceEditDialogPtr dlg;
  GrouP                  p, g;
  Int4                   i;
  Nlm_EnumFieldAssocPtr  eap;

  dlg = (InferenceEditDialogPtr) MemNew (sizeof (InferenceEditDialogData));
  if (dlg == NULL)
  {
    return NULL;
  }
  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  SetGroupSpacing (p, 10, 10);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = NULL;
  dlg->fromdialog = InferenceEditDataFromDialog;
  dlg->dialogmessage = NULL;
  dlg->testdialog = TestInferenceEditDialog;

  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->action = PopupList (p, TRUE, ChangeInferenceEditAction);
  SetObjectExtra (dlg->action, dlg, NULL);
  PopupItem (dlg->action, "Remove");
  PopupItem (dlg->action, "Change Category");
  PopupItem (dlg->action, "Change Type");
  PopupItem (dlg->action, "Apply Type Fields");
  PopupItem (dlg->action, "Edit Type Fields");
  SetValue (dlg->action, eInferenceRemove + 1);

  g = HiddenGroup (p, 0, 0, NULL);
  i = 0;

  /* remove group */
  dlg->action_pages[i] = HiddenGroup (g, -1, 0, NULL);
  StaticPrompt (dlg->action_pages[i], "Hit Accept to Remove Inferences", 0, popupMenuHeight, programFont, 'c');
  i++;

  /* edit category group */
  dlg->action_pages[i] = HiddenGroup (g, 2, 0, NULL);
  StaticPrompt (dlg->action_pages[i], "Original Category", 0, popupMenuHeight, programFont, 'c');
  dlg->category_from = PopupList (dlg->action_pages[i], TRUE, ChangeInferenceCategoryChoice);
  SetObjectExtra (dlg->category_from, dlg, NULL);
  PopupItem (dlg->category_from, "Any");
  eap = evcat_alist;
  while (eap->name != NULL) {
    PopupItem (dlg->category_from, eap->name);
    eap++;
  }
  
  StaticPrompt (dlg->action_pages[i], "New Category", 0, popupMenuHeight, programFont, 'c');
  dlg->category_to = PopupList (dlg->action_pages[i], TRUE, ChangeInferenceCategoryChoice);
  SetObjectExtra (dlg->category_to, dlg, NULL);
  InitEnumPopup (dlg->category_to, evcat_alist, NULL);
  i++;

  /* edit type group */
  dlg->action_pages[i] = HiddenGroup (g, 2, 0, NULL);
  StaticPrompt (dlg->action_pages[i], "Original Type", 0, popupMenuHeight, programFont, 'c');
  dlg->type_from = PopupList (dlg->action_pages[i], TRUE, ChangeInferenceCategoryChoice);
  SetObjectExtra (dlg->type_from, dlg, NULL);
  PopupItem (dlg->type_from, "Any");
  eap = inference_alist;
  while (eap->name != NULL) {
    PopupItem (dlg->type_from, eap->name);
    eap++;
  }
  
  StaticPrompt (dlg->action_pages[i], "New Type", 0, popupMenuHeight, programFont, 'c');
  dlg->type_to = PopupList (dlg->action_pages[i], TRUE, ChangeInferenceCategoryChoice);
  SetObjectExtra (dlg->type_to, dlg, NULL);
  InitEnumPopup (dlg->type_to, inference_alist, NULL);
  i++;

  dlg->action_pages[i] = HiddenGroup (g, 0, 0, NULL);
  dlg->apply_field = CreateInferenceFieldEditApplyDialog (dlg->action_pages[i], eEditApplyChoice_Apply, change_notify, change_userdata);
  i++;
  
  dlg->action_pages[i] = HiddenGroup (g, 0, 0, NULL);
  dlg->edit_field = CreateInferenceFieldEditApplyDialog (dlg->action_pages[i], eEditApplyChoice_Edit, change_notify, change_userdata);
  i++;
  
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->action_pages[0],
                              (HANDLE) dlg->action_pages[1],
                              (HANDLE) dlg->action_pages[2],
                              (HANDLE) dlg->action_pages[3],
                              NULL);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->action, (HANDLE) g, NULL);

  ChangeInferenceEditAction (dlg->action);

  return (DialoG) p;
}


/* This section of code is for handling ClickableLists */

typedef struct clickableitemlist
{
  DIALOG_MESSAGE_BLOCK
  DoC doc;

  Nlm_ParData par_fmt;
  Nlm_ColPtr col_fmt;

  ClickableCallback single_click_callback;
  ClickableCallback double_click_callback;
  Pointer           click_callback_data;
  GetClickableItemText get_item_text;
  ValNodePtr           item_list;
  Int2                 selected;
} ClickableItemListDlgData, PNTR ClickableItemListDlgPtr;

static void PointerToClickableItemListDlg (DialoG d, Pointer data)
{
  ClickableItemListDlgPtr dlg;
  ValNodePtr              vnp;
  CharPtr                 row_text;
  Int2                    numItems;
  RecT                    r;
  Int4                    i, count;

  dlg = (ClickableItemListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  Reset (dlg->doc);
  
  if (dlg->get_item_text == NULL)
  {
    return;
  }

  ObjectRect (dlg->doc, &r);
  InsetRect (&r, 4, 4);

  count = 1;
  i = 0;
  while (!dlg->col_fmt[i].last) {
    i++; 
    count++;
  }

  if (count > 1) {
    dlg->col_fmt[0].pixWidth = 5 * stdCharWidth;
    i = 0;
    while (!dlg->col_fmt[i].last) {
      i++; 
      dlg->col_fmt[i].pixWidth = (r.right - r.left - dlg->col_fmt[0].pixWidth) / (count - 1);
    }
  } else {
    dlg->col_fmt[0].pixWidth = r.right - r.left;
  }

  dlg->item_list = ClickableItemObjectListFree (dlg->item_list);
  dlg->item_list = (ValNodePtr) data; 
 
  if (dlg->item_list == NULL)
  {
    AppendText (dlg->doc, "No items listed", NULL, NULL, programFont);
  } else {
    for (vnp = dlg->item_list; vnp != NULL; vnp = vnp->next) {
      row_text = dlg->get_item_text (vnp);
      if (row_text != NULL)
      {
        if (vnp->choice == OBJ_SEQFEAT)
        {
          AppendText (dlg->doc, row_text, &(dlg->par_fmt), dlg->col_fmt, programFont);
        }
        else
        {
          AppendText (dlg->doc, row_text, &(dlg->par_fmt), NULL, programFont);
        }
        row_text = MemFree (row_text);
      }
    }
  }
  
  GetDocParams (dlg->doc, &numItems, NULL);
  UpdateDocument (dlg->doc, 0, numItems);  
}


static void ClickClickableItemList (DoC d, PoinT pt)

{
  Int2             item, last_selected, numItems;
  Int2             row, i;
  ClickableItemListDlgPtr dlg;
  ValNodePtr       vnp;

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    MapDocPoint (d, pt, &item, &row, NULL, NULL);
    if (item > 0 && row > 0) {  
      i = 1;
      vnp = dlg->item_list;
      while (i < item && vnp != NULL) {
        i++;
        vnp = vnp->next;
      }
      if (vnp != NULL) {
        last_selected = dlg->selected;
        dlg->selected = item;
        
        if (item != last_selected)
        {
          GetDocParams (d, &numItems, NULL);
          UpdateDocument (d, 0, numItems);
        }
    
        if (dblClick)
        {
          if (dlg->double_click_callback != NULL) {
            (dlg->double_click_callback) (vnp, dlg->click_callback_data);
          }
        } else {
          if (dlg->single_click_callback != NULL) {
            (dlg->single_click_callback) (vnp, dlg->click_callback_data);
          }
        }          
      }
    }
  }
}


static void DrawClickableItemList (DoC d, RectPtr r, Int2 item, Int2 firstLine)

{
  ClickableItemListDlgPtr dlg;
  RecT             rct;

  dlg = (ClickableItemListDlgPtr) GetObjectExtra (d);
  if (dlg != NULL && r != NULL && item > 0 && firstLine == 0) {
    rct = *r;
  
    /* draw selection */
    if (item == dlg->selected) {
      rct = *r;
      rct.right = rct.left + 4;
      PaintRect (&rct);
    }
  }
}


static void CleanupClickableItemListDlg (GraphiC g, VoidPtr data)
{
  ClickableItemListDlgPtr dlg;

  dlg = (ClickableItemListDlgPtr) data;
  if (dlg != NULL) {
    dlg->item_list = ClickableItemObjectListFree (dlg->item_list);
    dlg->col_fmt = MemFree (dlg->col_fmt);
  } 
  StdCleanupExtraProc (g, data);
}

static DialoG 
ClickableItemListDialog 
(GrouP h,
 Int4 width,
 GetClickableItemText get_item_text,
 Int4 num_item_text_columns,
 ClickableCallback single_click_callback,
 ClickableCallback double_click_callback,
 Pointer click_callback_data)
{
  ClickableItemListDlgPtr dlg;
  GrouP                p;
  Int4 i;

  dlg = (ClickableItemListDlgPtr) MemNew (sizeof (ClickableItemListDlgData));
  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupClickableItemListDlg);
  dlg->dialog = (DialoG) p;
  dlg->todialog = PointerToClickableItemListDlg;

  dlg->get_item_text = get_item_text;
  dlg->single_click_callback = single_click_callback;
  dlg->double_click_callback = double_click_callback;
  dlg->click_callback_data = click_callback_data;
  /* initialize paragraph format */
  dlg->par_fmt.openSpace = FALSE;
  dlg->par_fmt.keepWithNext = FALSE;
  dlg->par_fmt.keepTogether = FALSE;
  dlg->par_fmt.newPage = FALSE;
  dlg->par_fmt.tabStops = FALSE;
  dlg->par_fmt.minLines = 0;
  dlg->par_fmt.minHeight = 0;

  dlg->col_fmt = (Nlm_ColPtr) MemNew (sizeof (Nlm_ColData) * num_item_text_columns);
  /* initialize column format */
  for (i = 0; i < num_item_text_columns; i++) {
    dlg->col_fmt[i].pixWidth = 0;
    dlg->col_fmt[i].pixInset = 0;
    dlg->col_fmt[i].charWidth = 10;
    dlg->col_fmt[i].charInset = 0;
    dlg->col_fmt[i].font = NULL;
    dlg->col_fmt[i].just = 'l';
    dlg->col_fmt[i].wrap = 1;
    dlg->col_fmt[i].bar = 0;
    dlg->col_fmt[i].underline = 0;
    dlg->col_fmt[i].left = 0;
    dlg->col_fmt[i].last = FALSE;
  }
  dlg->col_fmt[0].pixInset = 5;
  dlg->col_fmt[num_item_text_columns - 1].last = TRUE;
  
  dlg->doc = DocumentPanel (p, width, stdLineHeight * 20);
  SetObjectExtra (dlg->doc, dlg, NULL);
  SetDocAutoAdjust (dlg->doc, FALSE);
  SetDocProcs (dlg->doc, ClickClickableItemList, NULL, NULL, NULL);
  SetDocShade (dlg->doc, DrawClickableItemList, NULL, NULL, NULL);

  return (DialoG) p;
}

typedef struct clickablelist
{
  DIALOG_MESSAGE_BLOCK
  DoC  doc;
  DialoG clickable_item_list;
  PrompT title1;
  PrompT title2;
  PrompT help1;
  PrompT help2;
  TexT find_txt;

  ClickableCallback item_single_click_callback;
  ClickableCallback item_double_click_callback;
  Pointer         item_click_callback_data;
  GetClickableItemText get_item_text;
  Boolean         dblClick;  
  Int2            clicked;
  Int2            selected;
  Int2            item_selected;  
  Int4            num_levels;
  ValNodePtr      list_list;
  Nlm_ColPtr PNTR col_fmt_array_array;
  PrompT          chosen_count;

  Int2            text_select_item_start;
  Int2            text_select_row_start;
  Int2            text_select_char_start;
  Int2            text_select_item_stop;
  Int2            text_select_row_stop;
  Int2            text_select_char_stop;
  Int2            text_select_item_anchor;
  Int2            text_select_row_anchor;
  Int2            text_select_char_anchor;

  Boolean         display_chosen;
} ClickableListData, PNTR ClickableListPtr;


static Nlm_ParData clickableParFmt = {FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0};
static Nlm_ColData clickableColFmt[2] = {{16, 0, 0, 0, NULL, 'l', 0,0,0,0, FALSE},
                                    {1000, 0, 0, 0, NULL, 'l', 1,0,0,0, TRUE}};


static Nlm_ColPtr PNTR FreeColumnFormatArrays (Nlm_ColPtr PNTR col_fmt_array_array, Int4 num_levels)
{
  Int4 n;
  
  if (col_fmt_array_array == NULL || num_levels < 1)
  {
    return NULL;
  }
  for (n = 0; n < num_levels; n++)
  {
    col_fmt_array_array [n] = MemFree (col_fmt_array_array [n]);
  }
  col_fmt_array_array = MemFree (col_fmt_array_array);
  return col_fmt_array_array;
}

static void CleanupClickableListDialog (GraphiC g, VoidPtr data)

{
  ClickableListPtr dlg;

  dlg = (ClickableListPtr) data;
  if (dlg != NULL) {
     dlg->col_fmt_array_array = FreeColumnFormatArrays (dlg->col_fmt_array_array, dlg->num_levels);
  }
  StdCleanupExtraProc (g, data);
}


static ClickableItemPtr GetSubItem (ValNodePtr item_list, Int2Ptr pitem)
{
  ClickableItemPtr cip = NULL;

  if (item_list == NULL || pitem == NULL)
  {
    return NULL;
  }
  while (*pitem > 0 && item_list != NULL)
  {
    (*pitem)--;
    cip = (ClickableItemPtr) item_list->data.ptrvalue;
    if (*pitem > 0)
    {
      if (cip != NULL && cip->expanded)
      {
        cip = GetSubItem (cip->subcategories, pitem);
      }
    }
    item_list = item_list->next;
  }
  if (*pitem > 0)
  {
    cip = NULL;
  }
  return cip;
}

static Int4 CountLevels (ValNodePtr item_list)
{
  Int4       num, num_sublevels = 0;
  ValNodePtr vnp;
  ClickableItemPtr cip;
  
  if (item_list == NULL) 
  {
    return 0;
  }
  
  for (vnp = item_list; vnp != NULL; vnp = vnp->next)
  {
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    if (cip == NULL || cip->subcategories == NULL || !cip->expanded)
    {
      continue;
    }
    num = CountLevels (cip->subcategories);
    if (num > num_sublevels) num_sublevels = num;
  }
  
  /* one level for the top plus levels for the subcategories */

  return 1 + num_sublevels;
}


static ClickableItemPtr GetSelectedClickableList (ValNodePtr item_list, Int2 item)
{
  ClickableItemPtr cip = NULL;
  
  cip = GetSubItem (item_list, &item);

  return cip;
}


static Nlm_ColPtr PNTR GetColumnFormatArrays (Int4 num_levels, DoC doc)
{
  Int4               n, k;
  Nlm_ColPtr PNTR    col_fmt_array_array = NULL;
  RecT               r;
  Int4               doc_width;
    
  if (num_levels == 0)
  {
    return NULL;
  }
  
  ObjectRect (doc, &r);
  InsetRect (&r, 4, 4);
  doc_width = r.right - r.left;
  
  col_fmt_array_array = (Nlm_ColPtr PNTR) MemNew (sizeof (Nlm_ColPtr) * num_levels);
  for (n = 0; n < num_levels; n++)
  {
    col_fmt_array_array[n] = (Nlm_ColPtr) MemNew (sizeof (Nlm_ColData) * (n + 3));
    for (k = 0; k < n + 2; k++)
    {
      col_fmt_array_array[n][k].pixWidth = 16;
      col_fmt_array_array[n][k].pixInset = 0;
      col_fmt_array_array[n][k].charWidth = 0;
      col_fmt_array_array[n][k].charInset = 0;
      col_fmt_array_array[n][k].font = programFont;
      col_fmt_array_array[n][k].just = 'l';
      col_fmt_array_array[n][k].wrap = 0;
      col_fmt_array_array[n][k].bar = 0;
      col_fmt_array_array[n][k].underline = 0;
      col_fmt_array_array[n][k].left = 0;
      col_fmt_array_array[n][k].last = 0;
    }
    col_fmt_array_array[n][k].pixWidth = doc_width - ((n + 2) * 16);
    col_fmt_array_array[n][k].pixInset = 0;
    col_fmt_array_array[n][k].charWidth = 0;
    col_fmt_array_array[n][k].charInset = 0;
    col_fmt_array_array[n][k].font = programFont;
    col_fmt_array_array[n][k].just = 'l';
    col_fmt_array_array[n][k].wrap = 1;
    col_fmt_array_array[n][k].bar = 0;
    col_fmt_array_array[n][k].underline = 0;
    col_fmt_array_array[n][k].left = 0;
    col_fmt_array_array[n][k].last = 1;
  }
  return col_fmt_array_array;
}


static void AddClickableItem (ClickableListPtr dlg, ClickableItemPtr cip, Int4 level)
{
  CharPtr            item_text;
  ValNodePtr         vnp;
  Int4               n;

  if (cip == NULL)
  {
    return;
  }
  item_text = (CharPtr) MemNew (sizeof (Char) * (StringLen (cip->description) + 6 + level));
  for (n = 0; n < level; n++)
  {
    StringCat (item_text, "\t");
  }
  StringCat (item_text, " \t \t");
  StringCat (item_text, cip->description);
  StringCat (item_text, "\n");
  AppendText (dlg->doc, item_text, &clickableParFmt, dlg->col_fmt_array_array [level], programFont);
  if (cip->expanded)
  {
    for (vnp = cip->subcategories; vnp != NULL; vnp = vnp->next)
    {
      AddClickableItem (dlg, vnp->data.ptrvalue, level + 1);
    }
  }
}

static void PopulateClickableList (ClickableListPtr dlg, ValNodePtr list_list)
{
  Int2               numItems;
  Int4               num_levels;
  
  if (dlg == NULL || dlg->doc == NULL) 
  {
    return;
  }
  
  Reset (dlg->doc);
  
  num_levels = CountLevels (dlg->list_list);
  if (num_levels != dlg->num_levels)
  {
    dlg->col_fmt_array_array = FreeColumnFormatArrays (dlg->col_fmt_array_array, dlg->num_levels);
    dlg->num_levels = num_levels;
    dlg->col_fmt_array_array = GetColumnFormatArrays (dlg->num_levels, dlg->doc);
  }
  
  while (list_list != NULL)
  {
    AddClickableItem (dlg, list_list->data.ptrvalue, 0);
    list_list = list_list->next;
  }
  GetDocParams (dlg->doc, &numItems, NULL);
  UpdateDocument (dlg->doc, 0, numItems);

}


NLM_EXTERN Int2 PanelOffsetFromCharOffsetEx (DoC doc, FonT font, Int2 item, Int2 col, Int2 char_offset)
{
  Int2 numRows, numCols, lineHeight;
  Int2 left_start, width, inset, char_width;

  if (doc == NULL) return 0;
  GetItemParams4 (doc, item, NULL, &numRows, &numCols, &lineHeight, NULL);
  GetColParams (doc, item, col, &left_start, &width, &inset, NULL);

  /* need to set font so that Nlm_CharWidth works properly */
  SelectFont (font);
  char_width = Nlm_CharWidth ('A');
  return left_start + inset + char_offset * char_width;
}


static Int2 PanelOffsetFromCharOffset (ClickableListPtr dlg, Int2 item, Int2 char_offset)
{
  Int2 numCols;
  if (dlg == NULL) return 0;
  GetItemParams4 (dlg->doc, item, NULL, NULL, &numCols, NULL, NULL);

  return PanelOffsetFromCharOffsetEx (dlg->doc, dlg->col_fmt_array_array[0][0].font, item, numCols, char_offset);
}


NLM_EXTERN Int2 GetTextSelectCharOffsetEx (PoinT pt, DoC doc, FonT font, Int2 item, Int2 row, Int2 col)
{
  Int2 pixPos, pixInset;
  Int2 one_char_width;
  CharPtr txt;
  Int4    len;
  Int2    char_offset;

  if (doc == NULL) return 0;

  GetColParams (doc, item, col, &pixPos,
                NULL, &pixInset, NULL);

  SelectFont (font);
  one_char_width = Nlm_CharWidth ('A');
  if (one_char_width == 0) return 0;

  char_offset = (pt.x - pixPos - pixInset) / one_char_width;
  if (char_offset > 0) {
    txt = GetDocText (doc, item, row, col);
    len = StringLen (txt);
    if (char_offset >= len) {
      char_offset = - 1;
    }
  }

  return char_offset; 
}


static Int2 GetTextSelectCharOffset (PoinT pt, ClickableListPtr dlg, Int2 item, Int2 row, Int2 col)
{
  if (dlg == NULL) return 0;

  return GetTextSelectCharOffsetEx (pt, dlg->doc, dlg->col_fmt_array_array[0][0].font, item, row, col);
}


static void ValNodeLinkCopy (ValNodePtr PNTR list1, ValNodePtr list2)
{
  if (list1 == NULL) return;
  while (list2 != NULL)
  {
    ValNodeAddPointer (list1, list2->choice, list2->data.ptrvalue);
    list2 = list2->next;
  }
}


static ValNodePtr GetChosenItemsList (ValNodePtr clickable_list)
{
  ValNodePtr item_list = NULL;
  ClickableItemPtr cip;

  while (clickable_list != NULL) {
    cip = (ClickableItemPtr) clickable_list->data.ptrvalue;
    if (cip != NULL) {
      if (cip->chosen) {
        ValNodeLinkCopy (&item_list, cip->item_list);
      } else {
        ValNodeLink (&item_list, GetChosenItemsList (cip->subcategories));
      }
    }
    clickable_list = clickable_list->next;
  }
  return item_list;
}


static void UpdateChosenCount (DialoG d)
{
  ClickableListPtr dlg;
  ValNodePtr list;
  Int4       num;
  CharPtr    fmt = "%d items selected";
  Char       val[255];

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return;
  }

  list = GetChosenItemsList (dlg->list_list);
  num = ValNodeLen (list);
  list = ClickableItemObjectListFree (list);
  sprintf (val, fmt, num);
  SetTitle (dlg->chosen_count, val);
}


static void ClickList (DoC d, PoinT pt)

{
  Int2             item, numItems;
  Int2             row;
  Int2             col;
  ClickableListPtr dlg;
  ClickableItemPtr cip;
  Int4             offset;
  Int2             first_shown;
  Boolean          rsult;
  RecT             r;

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    MapDocPoint (d, pt, &item, &row, &col, NULL);
    if (item > 0 && row > 0 && dlg->clicked == item) {
      dlg->dblClick = dblClick;
    } else {
      dlg->dblClick = FALSE;
    }
    dlg->clicked = 0;
    if (item > 0 && row > 0) {
      dlg->clicked = item;
    }
    if (item > 0 && row > 0 && !dblClick)
    {
      cip = GetSelectedClickableList (dlg->list_list, item);
      if (cip != NULL)
      {
        if (col == cip->level + 1)
        {
          cip->chosen = !cip->chosen;
          GetDocParams (d, &numItems, NULL);
          UpdateDocument (d, 0, numItems);
          if (dlg->display_chosen) 
          {
            PointerToDialog (dlg->clickable_item_list, GetChosenItemsList (dlg->list_list));
            UpdateChosenCount (dlg->dialog);
          }
        }
        else if (col == cip->level + 2)
        {
          if (cip->subcategories != NULL) {
            cip->expanded = !cip->expanded;
            rsult = GetScrlParams4 (dlg->doc, &offset, &first_shown, NULL);
            PopulateClickableList (dlg, dlg->list_list);
            if (rsult) {
              GetItemParams4 (dlg->doc, first_shown, &offset, NULL, NULL, NULL, NULL);
              SetScrlParams4 (dlg->doc, offset);
            }
            ObjectRect (dlg->doc, &r);
            InsetRect (&r, -1, -1);
            InvalRect (&r);
          }
        } else {
          dlg->text_select_item_anchor = item;
          dlg->text_select_row_anchor = row;
          dlg->text_select_char_anchor = GetTextSelectCharOffset (pt, dlg, item, row, col);

          dlg->text_select_char_start = dlg->text_select_char_anchor;
          dlg->text_select_char_stop = dlg->text_select_char_start;
          if (dlg->text_select_char_start < 0) {
            dlg->text_select_item_start = -1;
            dlg->text_select_row_start = -1;
            dlg->text_select_item_stop = -1;
            dlg->text_select_row_stop = -1;
          } else {
            dlg->text_select_item_start = item;
            dlg->text_select_row_start = row;
            dlg->text_select_item_stop = item;
            dlg->text_select_row_stop = row;
          }
          GetDocParams (dlg->doc, &numItems, NULL);
          UpdateDocument (dlg->doc, 0, numItems);
        }
      }
    }
  }
}

static void UpdateClickableListTextSelection (ClickableListPtr dlg, Int2 item, Int2 row, Int2 col, PoinT pt)
{
  Int2 char_start, numCols;

  if (dlg == NULL || item < 1 || row < 1) return;

  /* only update for positions in the text area */
  GetItemParams4 (dlg->doc, item, NULL, NULL, &numCols, NULL, NULL);
  if (col != numCols) {
    return;
  }

  char_start = GetTextSelectCharOffset (pt, dlg, item, row, col);
  if (char_start < 0) {
    /* no updates unless mouse is in text area */
    return;
  }
    
  if (item < dlg->text_select_item_anchor) {
    /* mouse is before anchor */
    dlg->text_select_item_start = item;
    dlg->text_select_row_start = row;
    dlg->text_select_char_start = char_start;
    dlg->text_select_item_stop = dlg->text_select_item_anchor;
    dlg->text_select_row_stop = dlg->text_select_row_anchor;
    dlg->text_select_char_stop = dlg->text_select_char_anchor;
  } else if (item == dlg->text_select_item_anchor) {
    /* start and stop in the anchor item */
    dlg->text_select_item_start = item;
    dlg->text_select_item_stop = item;
    if (row < dlg->text_select_row_anchor) {
      /* mouse is before anchor */
      dlg->text_select_row_start = row;
      dlg->text_select_char_start = char_start;
      dlg->text_select_row_stop = dlg->text_select_row_anchor;
      dlg->text_select_char_stop = dlg->text_select_char_anchor;
    } else if (row == dlg->text_select_row_anchor) {
      /* start and stop in the anchor row */
      dlg->text_select_row_start = row;
      dlg->text_select_row_stop = row;
      if (char_start <= dlg->text_select_char_anchor) {
        /* mouse is before anchor */
        dlg->text_select_char_start = char_start;
        dlg->text_select_char_stop = dlg->text_select_char_anchor;
      } else {
        dlg->text_select_char_start = dlg->text_select_char_anchor;
        dlg->text_select_char_stop = char_start;
      }
    }
  } else {
    /* mouse is after anchor */
    dlg->text_select_item_start = dlg->text_select_item_anchor;
    dlg->text_select_row_start = dlg->text_select_row_anchor;
    dlg->text_select_char_start = dlg->text_select_char_anchor;
    dlg->text_select_item_stop = item;
    dlg->text_select_row_stop = row;
    dlg->text_select_char_stop = char_start;
  }
  InvalDocRows (dlg->doc, 0, 0, 0);
}

static void DragClickableList (DoC d, PoinT pt)
{
  Int2            item, row, col, numCols;

  ClickableListPtr dlg;

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    MapDocPoint (d, pt, &item, &row, &col, NULL);
    GetItemParams4 (d, item, NULL, NULL, &numCols, NULL, NULL);
    if (col == numCols) {
      UpdateClickableListTextSelection (dlg, item, row, col, pt);
    }
  }
}


static void InvalBorder (DoC d, Int2 item)

{
  Int2  bottom;
  RecT  r;
  Int2  top;

  ObjectRect (d, &r);
  InsetRect (&r, 4, 4);
  if (ItemIsVisible (d, item, &top, &bottom, NULL)) {
    r.top = top;
    r.bottom = bottom;
    r.right = r.left + 4;
    InsetRect (&r, -1, -1);
    InvalRect (&r);
  }
}

static void ActOnClickableList (ValNodePtr list_list, Int2 item)
{
  ClickableItemPtr cip;
  
  cip = GetSelectedClickableList (list_list, item);
  if (cip != NULL && cip->callback_func != NULL)
  {
    (cip->callback_func) (cip->item_list, cip->callback_data);
  }
}


static void PopulateClickableItemList (DialoG d, ClickableItemPtr cip)
{
  ValNodePtr        list = NULL;
  
  if (d == NULL) return;
  if (cip != NULL)
  {
    ValNodeLink (&list, ClickableItemObjectListCopy (cip->item_list));
  }
  PointerToDialog (d, list);

}


static void ReleaseClickableList (DoC d, PoinT pt)

{
  Int2           item;
  Int2           old;
  Int2           row, col;
  ClickableListPtr dlg;

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    ResetClip ();
    MapDocPoint (d, pt, &item, &row, &col, NULL);
    /* update text selection */
    UpdateClickableListTextSelection (dlg, item, row, col, pt);

    if (item > 0 && row > 0) {

      if (item == dlg->clicked) {
        old = dlg->selected;
        dlg->selected = item;
        if (old != item) {
          if (old == 0) {
            UpdateDocument (d, item, item);
          } else {
            UpdateDocument (d, old, old);
            UpdateDocument (d, item, item);
          }
          Update ();
        }
      }
    } else if (dlg->clicked == 0) {
      if (dlg->selected != 0) {
        old = dlg->selected;
        dlg->selected = 0;
        InvalBorder (d, old);
      }
      Update ();
    }
    if (dlg->selected > 0 && dlg->dblClick)
    {
      ActOnClickableList (dlg->list_list, dlg->selected);
    }
    else if (dlg->selected > 0)
    {
      dlg->item_selected = 0;
      if (!dlg->display_chosen) {
        PopulateClickableItemList (dlg->clickable_item_list, 
                                  GetSelectedClickableList (dlg->list_list,
                                                            dlg->selected));
        UpdateChosenCount (dlg->dialog);
      }
    }
  }
}


static void DrawTextSelection (ClickableListPtr dlg, Int2 item, RectPtr r)
{
  Int2 lineHeight, numRows, numCols;
  Int2 last_right;
  Int2 left_start;
  Int4 top, line_y;
  CharPtr txt;

  if (dlg == NULL || r == NULL
      || item < dlg->text_select_item_start
      || item > dlg->text_select_item_stop) {
    return;
  }

  if (dlg->text_select_item_start == dlg->text_select_item_stop
      && dlg->text_select_row_start == dlg->text_select_row_stop
      && dlg->text_select_char_start == dlg->text_select_char_stop) {
    /* if we've only selected one char, and it's blank, don't draw it. */
    txt = GetSelectedClickableListText (dlg->dialog);
    if (StringHasNoText (txt)) {
      MemFree (txt);
      return;
    }
    MemFree (txt);
  }

  GetItemParams4 (dlg->doc, item, &top, &numRows, &numCols, &lineHeight, NULL);

  /* calculate missing rows from end first */
  if (dlg->text_select_item_stop == item) {
    numRows = dlg->text_select_row_stop;
    last_right = PanelOffsetFromCharOffset (dlg, item, dlg->text_select_char_stop + 1);
  } else {
    last_right = r->right;
  }

  if (dlg->text_select_item_start == item) {
    left_start = PanelOffsetFromCharOffset (dlg, item, dlg->text_select_char_start);
    line_y = r->top + (dlg->text_select_row_start) * lineHeight - 1;
    numRows -= dlg->text_select_row_start - 1;
  } else {
    left_start = PanelOffsetFromCharOffset (dlg, item, 0);
    line_y = r->top + lineHeight - 1;
  }

  while (numRows > 1) {
    MoveTo (left_start, line_y);
    left_start = PanelOffsetFromCharOffset (dlg, item, 0);
    LineTo (r->right, line_y);
    line_y += lineHeight;
    numRows--;
  }
  MoveTo (left_start, line_y);
  LineTo (last_right, line_y);

}

static void DrawClickableList (DoC d, RectPtr r, Int2 item, Int2 firstLine)

{
  ClickableListPtr dlg;
  RecT             rct;
  ClickableItemPtr cip;
  Int4             level_offset;

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg != NULL && r != NULL && item > 0 && firstLine == 0) {
    rct = *r;
  
    cip = GetSelectedClickableList (dlg->list_list, item);
    if (cip != NULL)
    {
      level_offset = cip->level * 16;
      rct.left += level_offset;
      rct.right += level_offset;
    }

    /* draw text selection */
    DrawTextSelection (dlg, item, &rct);

    /* draw selection */
    if (item == dlg->selected) {
      rct.right = rct.left + 4;
      PaintRect (&rct);
    }

    /* draw chosen checkboxes */
    rct.left += 5;
    rct.right = rct.left + 10;
    rct.bottom = rct.top + (rct.right - rct.left);
    FrameRect (&rct);
    
    if (cip != NULL && cip->chosen) {
      MoveTo (rct.left, rct.top);
      LineTo (rct.right - 1, rct.bottom - 1);
      MoveTo (rct.left, rct.bottom - 1);
      LineTo (rct.right - 1, rct.top);
    }
    
    /* draw open/closed checkboxes */
    if (cip!= NULL && cip->subcategories != NULL)
    {
      rct.left += 10;
      rct.right = rct.left + 10;
      rct.bottom = rct.top + (rct.right - rct.left);
      FrameRect (&rct);
      MoveTo (rct.left, (rct.top + rct.bottom) / 2);
      LineTo (rct.right - 1, (rct.top + rct.bottom) / 2);
      if (!cip->expanded)
      {
        MoveTo ((rct.left + rct.right) / 2, rct.top);
        LineTo ((rct.left + rct.right) / 2, rct.bottom - 1);
      }
    }

  }
}



static void DrawClickableListItem (DoC d, RectPtr r, Int2 item, Int2 firstLine)

{
  ClickableListPtr dlg;
  RecT             rct;

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg != NULL && r != NULL && item > 0 && firstLine == 0) {
    rct = *r;
  
    /* draw selection */
    if (item == dlg->item_selected) {
      rct = *r;
      rct.right = rct.left + 4;
      PaintRect (&rct);
    }
  }
}


static void ClickClickableListItem (DoC d, PoinT pt)

{
  Int2             item, last_selected, numItems;
  Int2             row;
  ClickableListPtr dlg;
  ClickableItemPtr cip;
  ValNodePtr       vnp;

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    MapDocPoint (d, pt, &item, &row, NULL, NULL);
    if (item > 0 && row > 0) {  
      cip = GetSelectedClickableList (dlg->list_list, dlg->selected);
      if (cip != NULL && cip->item_list != NULL)
      {
        vnp = cip->item_list;
        
        last_selected = dlg->item_selected;
        dlg->item_selected = item;
        
        if (item != last_selected)
        {
          GetDocParams (d, &numItems, NULL);
          UpdateDocument (d, 0, numItems);
        }
    
        /* find item in list */
        while (item > 1 && vnp != NULL)
        {
          vnp = vnp->next;
          item--;
        }
        
        if (dblClick)
        {
          if (dlg->item_double_click_callback != NULL) {
            (dlg->item_double_click_callback) (vnp, dlg->item_click_callback_data);
          }
        } else {
          if (dlg->item_single_click_callback != NULL) {
            (dlg->item_single_click_callback) (vnp, dlg->item_click_callback_data);
          }
        }          
      }
    }
  }
}


static void ClickableListToDialog (DialoG d, Pointer userdata)
{
  ClickableListPtr dlg;

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  dlg->list_list = (ValNodePtr) userdata;

  PopulateClickableList (dlg, dlg->list_list);
  if (dlg->list_list != NULL) {
    dlg->selected = 1;
    dlg->item_selected = 0;
    if (dlg->display_chosen)  {
      PointerToDialog (dlg->clickable_item_list, GetChosenItemsList (dlg->list_list));
      UpdateChosenCount (dlg->dialog);
    } else {
      PopulateClickableItemList (dlg->clickable_item_list, 
                                GetSelectedClickableList (dlg->list_list,
                                                          dlg->selected));
    }
  } else {
    PointerToDialog (dlg->clickable_item_list, NULL);
  }
}

static void FindClickableText (ButtoN b) 
{
  ClickableListPtr dlg;
  CharPtr          find_txt;

  dlg = (ClickableListPtr) GetObjectExtra (b);
  if (dlg == NULL)
  {
    return;
  }
  
  find_txt = SaveStringFromText (dlg->find_txt);
  ScrollToNextClickableTextDescription (find_txt, dlg->dialog);
  find_txt = MemFree (find_txt);
}


static void FindPreviousClickableText (ButtoN b)
{
  ClickableListPtr dlg;
  CharPtr          find_txt;

  dlg = (ClickableListPtr) GetObjectExtra (b);
  if (dlg == NULL)
  {
    return;
  }
  
  find_txt = SaveStringFromText (dlg->find_txt);
  ScrollToPreviousClickableTextDescription (find_txt, dlg->dialog);
  find_txt = MemFree (find_txt);
}


static void ClickableListCopyToClipboard (DialoG d)
{
  ClickableListPtr dlg;
  CharPtr          txt;
  
  dlg = (ClickableListPtr) GetObjectExtra (d);

  if (dlg == NULL) return;

  txt = GetSelectedClickableListText (d);
  Nlm_StringToClipboard (txt);
  txt = MemFree (txt);
}

static void ClickableListMessage (DialoG d, Int2 mssg)

{
  ClickableListPtr dlg;

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (mssg == VIB_MSG_COPY) {
      ClickableListCopyToClipboard(d);
    }
  }
}

static void ClickableListOnKey (SlatE s, Char ch)
{
  DoC              d;
  ClickableListPtr clp;
#ifdef WIN_MSWIN
  Int2             first_shown;
  Int4             offset;
  BaR              sb;
#endif

  if ( (int) ch == 0 ) return;

  d = (DoC) s;
  clp = (ClickableListPtr) GetObjectExtra (d);

  CaptureSlateFocus (s);
  /* later, handle control key combos */
#ifdef WIN_MSWIN
  if (ch == 3)
  {
    ClickableListCopyToClipboard (clp->dialog);
  }
  else if (ch == 11) 
  {
    /* PageUp key pressed */
    if (GetScrlParams4 (d, &offset, &first_shown, NULL) && first_shown > 0) 
    {
      sb = GetSlateVScrollBar (s);
      Nlm_Scroll (sb, SCROLL_PAGEUP);
    }
  }
  else if (ch == 12)
  {
    /* PageDown key pressed */
    if (GetScrlParams4 (d, &offset, &first_shown, NULL)) 
    {
      sb = GetSlateVScrollBar (s);
      Nlm_Scroll (sb, SCROLL_PAGEDN);
    }
  }
  else if (ch == 30)
  {
    /* Up Arrow key pressed */
    if (GetScrlParams4 (d, &offset, &first_shown, NULL) && first_shown > 0) 
    {
      GetItemParams4 (d, first_shown - 1, &offset, NULL, NULL, NULL, NULL);
      SetScrlParams4 (d, offset);
    }
  }
  else if (ch == 31)
  {
    /* Down Arrow key pressed */
    if (GetScrlParams4 (d, &offset, &first_shown, NULL)) {
      sb = GetSlateVScrollBar (s);
      if (offset < GetBarMax (sb)) 
      {
        GetItemParams4 (d, first_shown + 1, &offset, NULL, NULL, NULL, NULL);
        SetScrlParams4 (d, offset);
      }
    }
  }
#endif
}


extern DialoG 
CreateClickableListDialogExExEx 
(GrouP h, 
 CharPtr label1, 
 CharPtr label2,
 CharPtr help1,
 CharPtr help2,
 ClickableCallback item_single_click_callback,
 ClickableCallback item_double_click_callback,
 Pointer         item_click_callback_data,
 GetClickableItemText get_item_text,
 Int4            num_item_text_columns,
 Int4            left_width,
 Int4            right_width,
 Boolean         horizontal,
 Boolean         show_find,
 Boolean         display_chosen)
{
  GrouP p, pnl_grp, find_grp = NULL;
  ClickableListPtr dlg;
  RecT             r;
  ButtoN           b;

  dlg = (ClickableListPtr) MemNew (sizeof (ClickableListData));
  if (dlg == NULL)
  {
    return NULL;
  }
  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupClickableListDialog);
  SetGroupSpacing (p, 10, 10);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = ClickableListToDialog;
  dlg->fromdialog = NULL;
  dlg->dialogmessage = ClickableListMessage;
  dlg->testdialog = NULL;
  
  dlg->item_single_click_callback = item_single_click_callback;
  dlg->item_double_click_callback = item_double_click_callback;
  dlg->item_click_callback_data = item_click_callback_data;
  
  dlg->get_item_text = get_item_text;

  dlg->display_chosen = display_chosen;
  
  if (horizontal) {
    pnl_grp = HiddenGroup (p, 2, 0, NULL);
    
    if (label1 || label2) {
      dlg->title1 = StaticPrompt (pnl_grp, label1, left_width, popupMenuHeight, programFont, 'c');
      dlg->title2 = StaticPrompt (pnl_grp, label2, right_width, popupMenuHeight, programFont, 'c');
    }
    dlg->doc = DocumentPanel (pnl_grp, left_width, stdLineHeight * 20);
    dlg->clickable_item_list = ClickableItemListDialog (pnl_grp, right_width, 
                                                        get_item_text, num_item_text_columns,
                                                        item_single_click_callback,
                                                        item_double_click_callback, 
                                                        item_click_callback_data);
    if (help1 || help2) {
      dlg->help1 = StaticPrompt (pnl_grp, help1, left_width, popupMenuHeight, programFont, 'c');
      dlg->help2 = StaticPrompt (pnl_grp, help2, right_width, popupMenuHeight, programFont, 'c');
    } 
  } else {
    pnl_grp = HiddenGroup (p, -1, 0, NULL);
    dlg->title1 = StaticPrompt (pnl_grp, label1, left_width, popupMenuHeight, programFont, 'c');
    dlg->doc = DocumentPanel (pnl_grp, left_width, stdLineHeight * 20);
    if (help1 || help2) {
      dlg->help1 = StaticPrompt (pnl_grp, help1, left_width, popupMenuHeight, programFont, 'c');
    }
    dlg->title2 = StaticPrompt (pnl_grp, label2, right_width, popupMenuHeight, programFont, 'c');
    dlg->clickable_item_list = ClickableItemListDialog (pnl_grp, right_width, 
                                                        get_item_text, num_item_text_columns,
                                                        item_single_click_callback,
                                                        item_double_click_callback, 
                                                        item_click_callback_data);
    if (help1 || help2) {
      dlg->help2 = StaticPrompt (pnl_grp, help2, right_width, popupMenuHeight, programFont, 'c');
    }
    AlignObjects (ALIGN_CENTER, (HANDLE) dlg->title1, (HANDLE) dlg->doc, (HANDLE) dlg->title2, (HANDLE) dlg->clickable_item_list, 
                                (HANDLE) (dlg->help1 == NULL ? dlg->help2 : dlg->help1), (HANDLE) (dlg->help1 == NULL ? NULL : dlg->help2), NULL);
  }

  SetObjectExtra (dlg->doc, dlg, NULL);
  SetDocAutoAdjust (dlg->doc, FALSE);
  SetDocProcs (dlg->doc, ClickList, DragClickableList, ReleaseClickableList, NULL);
  SetDocShade (dlg->doc, DrawClickableList, NULL, NULL, NULL);
  
  SetSlateChar ((SlatE) dlg->doc, ClickableListOnKey);
  
  /* adjust column width for discrepancy list */
  ObjectRect (dlg->doc, &r);
  InsetRect (&r, 4, 4);
  clickableColFmt[1].pixWidth = r.right - r.left - clickableColFmt[0].pixWidth;
  
  dlg->chosen_count = StaticPrompt (p, "0 items checked", 20 * stdCharWidth, dialogTextHeight, programFont, 'l');
  if (!dlg->display_chosen) {
    Hide (dlg->chosen_count);
  }
  if (show_find) {
    find_grp = HiddenGroup (p, 4, 0, NULL);
    SetGroupSpacing (find_grp, 10, 10);
    StaticPrompt (find_grp, "Find Text", 0, popupMenuHeight, programFont, 'l');
    dlg->find_txt = DialogText (find_grp, "", 20, NULL);
    b = PushButton (find_grp, "<<", FindPreviousClickableText);
    SetObjectExtra (b, dlg, NULL);
    b = PushButton (find_grp, ">>", FindClickableText);
    SetObjectExtra (b, dlg, NULL);
  }
  
  AlignObjects (ALIGN_CENTER, (HANDLE) pnl_grp, (HANDLE) dlg->chosen_count, (HANDLE) find_grp, NULL);
  return (DialoG) p;
}


extern DialoG 
CreateClickableListDialogExEx 
(GrouP h, 
 CharPtr label1, 
 CharPtr label2,
 CharPtr help1,
 CharPtr help2,
 ClickableCallback item_single_click_callback,
 ClickableCallback item_double_click_callback,
 Pointer         item_click_callback_data,
 GetClickableItemText get_item_text,
 Int4            left_width,
 Int4            right_width,
 Boolean         horizontal,
 Boolean         show_find,
 Boolean         display_chosen)
{
  return CreateClickableListDialogExExEx (h, label1, label2, help1, help2,
                                          item_single_click_callback,
                                          item_double_click_callback,
                                          item_click_callback_data,
                                          get_item_text, 4,
                                          left_width,
                                          right_width,
                                          horizontal,
                                          show_find,
                                          display_chosen);
}


extern DialoG 
CreateClickableListDialogEx 
(GrouP h, 
 CharPtr label1, 
 CharPtr label2,
 CharPtr help1,
 CharPtr help2,
 ClickableCallback item_single_click_callback,
 ClickableCallback item_double_click_callback,
 Pointer         item_click_callback_data,
 GetClickableItemText get_item_text,
 Int4            left_width,
 Int4            right_width,
 Boolean         horizontal,
 Boolean         show_find)
{
  return CreateClickableListDialogExEx (h, label1, label2, help1, help2, 
                                        item_single_click_callback, item_double_click_callback,
                                        item_click_callback_data,
                                        get_item_text,
                                        left_width,
                                        right_width,
                                        horizontal,
                                        show_find,
                                        FALSE);
}


extern void SetClickableListDialogTitles (DialoG d, CharPtr title1, CharPtr title2, CharPtr help1, CharPtr help2)
{
  ClickableListPtr dlg;

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  SafeSetTitle (dlg->title1, title1);
  SafeSetTitle (dlg->title2, title2);
  SafeSetTitle (dlg->help1, help1);
  SafeSetTitle (dlg->help2, help2);
}


extern void SetClickableListDisplayChosen (DialoG d, Boolean set)
{
  ClickableListPtr dlg;

  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  dlg->display_chosen = set;
  if (dlg->display_chosen)  {
    PointerToDialog (dlg->clickable_item_list, GetChosenItemsList (dlg->list_list));
    UpdateChosenCount (d);
    Show (dlg->chosen_count);
  } else {
    PopulateClickableItemList (dlg->clickable_item_list, 
                              GetSelectedClickableList (dlg->list_list,
                                                        dlg->selected));
    Hide (dlg->chosen_count);
  }
}  


extern DialoG 
CreateClickableListDialog 
(GrouP h, 
 CharPtr label1, 
 CharPtr label2,
 ClickableCallback item_single_click_callback,
 ClickableCallback item_double_click_callback,
 Pointer         item_click_callback_data,
 GetClickableItemText get_item_text)
{
  return CreateClickableListDialogEx (h, label1, label2, NULL, NULL,
                                      item_single_click_callback,
                                      item_double_click_callback,
                                      item_click_callback_data,
                                      get_item_text, 
                                      stdCharWidth * 30,
                                      stdCharWidth * 30 + 5,
                                      TRUE, TRUE);
}


static Int4 CountExpandedSublevels (ValNodePtr subcategories)
{
  ValNodePtr vnp;
  Int4       num = 0;
  ClickableItemPtr cip;

  for (vnp = subcategories; vnp != NULL; vnp = vnp->next) {
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    if (cip != NULL) {
      num++;
      if (cip->expanded && cip->subcategories != NULL) {
        num += CountExpandedSublevels(cip->subcategories);
      }
    }
  }
  return num;
}


/* First, need to locate items that have description that contains txt.
 * Then need to make sure that they are visible.
 * Then need to figure out how to scroll to them.
 */
static Boolean FindInClickableItemDescriptions (CharPtr txt, ValNodePtr clickable_item_list, ValNodePtr PNTR row_list, Int4Ptr row_offset)
{
  ValNodePtr       vnp;
  ClickableItemPtr cip;
  Boolean          found = FALSE;
  Int4             lower_levels;
  
  if (StringHasNoText (txt) || clickable_item_list == NULL || row_list == NULL || row_offset == NULL) return FALSE;
  
  vnp = clickable_item_list;
  while (vnp != NULL) {
    (*row_offset)++;
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    if (StringStr (cip->description, txt) != NULL) {
      ValNodeAddInt (row_list, 0, *row_offset);
      found = TRUE;
    }
    lower_levels = *row_offset;
    if (cip->subcategories != NULL) {
      if (FindInClickableItemDescriptions (txt, cip->subcategories, row_list, &lower_levels)) {
        cip->expanded = TRUE;
        found = TRUE;
      }
      if (cip->expanded) {
        (*row_offset) += CountExpandedSublevels (cip->subcategories);
      }
    }
    vnp = vnp->next;
  }
  
  return found;
}


extern void ScrollToNextClickableTextDescription (CharPtr txt, DialoG d)
{
  ClickableListPtr dlg;
  ValNodePtr       found_list = NULL, vnp;
  Int4             row_offset = 0, startsAt;
  Int2             numRows, numCols, lineHeight;
  
  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }

  if (!FindInClickableItemDescriptions (txt, dlg->list_list, &found_list, &row_offset)
      || found_list == NULL) {
    Message (MSG_OK, "Text not found!");
    return;
  }
 
  /* find first found txt after current selection */
  vnp = found_list;
  while (vnp != NULL && vnp->data.intvalue <= dlg->selected) {
    vnp = vnp->next;
  }
  if (vnp == NULL) {
   row_offset = found_list->data.intvalue;
  } else {
    row_offset = vnp->data.intvalue;
  }

  dlg->selected = row_offset; 
  PopulateClickableList (dlg, dlg->list_list);
  SetDocAutoAdjust (dlg->doc, TRUE);   
  UpdateDocument (dlg->doc, 0, 0);
  SetDocAutoAdjust (dlg->doc, FALSE);
  dlg->item_selected = 0;
  PopulateClickableItemList (dlg->clickable_item_list, 
                             GetSelectedClickableList (dlg->list_list,
                                                       dlg->selected));
 
  GetItemParams4 (dlg->doc, row_offset, &startsAt, &numRows,
                  &numCols, &lineHeight, NULL);
  SetScrlParams4 (dlg->doc, startsAt);
}


extern void ScrollToPreviousClickableTextDescription (CharPtr txt, DialoG d)
{
  ClickableListPtr dlg;
  ValNodePtr       found_list = NULL, vnp, vnp_prev = NULL;
  Int4             row_offset = 0, startsAt;
  Int2             numRows, numCols, lineHeight;
  
  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }

  if (!FindInClickableItemDescriptions (txt, dlg->list_list, &found_list, &row_offset)
      || found_list == NULL) {
    Message (MSG_OK, "Text not found!");
    return;
  }
 
  /* find first found txt before current selection */
  vnp = found_list;
  while (vnp != NULL && vnp->data.intvalue < dlg->selected) {
    vnp_prev = vnp;
    vnp = vnp->next;
  }
  if (vnp_prev == NULL) {
    /* use last item in list */
    vnp = found_list;
    while (vnp != NULL && vnp->next != NULL) {
      vnp = vnp->next;
    }
    if (vnp != NULL) {
      row_offset = vnp->data.intvalue;
    }
  } else {
    row_offset = vnp_prev->data.intvalue;
  }

  dlg->selected = row_offset; 
  PopulateClickableList (dlg, dlg->list_list);
  SetDocAutoAdjust (dlg->doc, TRUE);   
  UpdateDocument (dlg->doc, 0, 0);
  SetDocAutoAdjust (dlg->doc, FALSE);
  dlg->item_selected = 0;
  PopulateClickableItemList (dlg->clickable_item_list, 
                             GetSelectedClickableList (dlg->list_list,
                                                       dlg->selected));
 
  GetItemParams4 (dlg->doc, row_offset, &startsAt, &numRows,
                  &numCols, &lineHeight, NULL);
  SetScrlParams4 (dlg->doc, startsAt);
}


static CharPtr GetClickableTextFromItemList (ValNodePtr item_list, CharPtr separator) 
{
  Int4 text_len = 1;
  ValNodePtr vnp;
  CharPtr    txt;

  for (vnp = item_list; vnp != NULL; vnp = vnp->next) {
    text_len += StringLen (vnp->data.ptrvalue) + StringLen (separator);
  }

  txt = (CharPtr) MemNew (sizeof(Char) * text_len);
  for (vnp = item_list; vnp != NULL; vnp = vnp->next) {
    StringCat (txt, vnp->data.ptrvalue);
    if (vnp->next != NULL) {
      StringCat (txt, separator);
    }
  }
  return txt;
}


static CharPtr
GetFragmentFromDocRow
(DoC  doc,
 Int2 item,
 Int2 col,
 Int2 row,
 Int2 char_start, Int2 char_stop)
{
  CharPtr txt = NULL, cp_txt;
  Int4    len;
 
  txt = GetDocText (doc, item, row, col);

  if (char_start == 0 && char_stop < 0) {
    /* take all of row */
    return txt;
  } else {
    len = StringLen (txt);
    if (char_stop >= len) {
      /* selection was drawn beyond length of text */
      char_stop = len - 1;
    } else if (char_stop < 0) {
      /* take all of row */
      char_stop = len - 1;
    }
    if (char_start >= len) {
      /* do nothing - selection is not within text */
      txt = MemFree (txt);
    } else {
      cp_txt = (CharPtr) MemNew (sizeof (Char) * (2 + char_stop - char_start));
      StringNCpy (cp_txt, txt + char_start, 1 + char_stop - char_start);
      cp_txt[1 + char_stop - char_start] = 0;
      txt = MemFree (txt);
      txt = cp_txt;
    }
  }  
  return txt;
}


static CharPtr 
GetFragmentsFromDocCol
(DoC  doc,
 Int2 item,
 Int2 col,
 Int2 row_start, Int2 row_stop,
 Int2 char_start, Int2 char_stop)
{
  Int2       row;
  CharPtr    txt;
  ValNodePtr fragments = NULL;

  if (row_start == row_stop) {
    txt = GetFragmentFromDocRow (doc, item, col, row_start, char_start, char_stop);
    if (StringHasNoText (txt)) {
      txt = MemFree (txt);
    } else {
      ValNodeAddPointer (&fragments, 0, txt);
    }
  } else {
    txt = GetFragmentFromDocRow (doc, item, col, row_start, char_start, -1);
    if (StringHasNoText (txt)) {
      txt = MemFree (txt);
    } else {
      ValNodeAddPointer (&fragments, 0, txt);
    }
    row = row_start + 1;
    while (row < row_stop) {
      txt = GetFragmentFromDocRow (doc, item, col, row, 0, -1);
      if (StringHasNoText (txt)) {
        txt = MemFree (txt);
      } else {
        ValNodeAddPointer (&fragments, 0, txt);
      }
      row++;
    }
    txt = GetFragmentFromDocRow (doc, item, col, row_stop, 0, char_stop);
    if (StringHasNoText (txt)) {
      txt = MemFree (txt);
    } else {
      ValNodeAddPointer (&fragments, 0, txt);
    }
  }
  txt = GetClickableTextFromItemList (fragments, " ");
  fragments = ValNodeFreeData (fragments);
  return txt;
}


static Boolean CollectFromThisColumn (Int2 col, Int2Ptr only_these_columns, Int2 num_col)
{
  Int2 i;
  Boolean rval = FALSE;

  if (only_these_columns == NULL) {
    rval = TRUE;
  } else {
    for (i = 0; i < num_col; i++) {
      if (only_these_columns[i] == col) {
        rval = TRUE;
      }
    }
  }
  return rval;
}

 
static CharPtr
GetFragmentsFromDocItem 
(DoC  doc,
 Int2 item, 
 Int2 col_start, Int2 col_stop,
 Int2 row_start, Int2 row_stop,
 Int2 char_start, Int2 char_stop,
 Int2Ptr only_these_columns, Int2 num_col)
{
  Int2       col;
  CharPtr    txt;
  Int2       num_rows, num_cols;
  ValNodePtr fragments = NULL;

  GetItemParams4 (doc, item, NULL, &num_rows, &num_cols, NULL, NULL);
  if (col_stop < 0) {
    col_stop = num_cols;
  }
  if (row_stop < 0) {
    row_stop = num_rows;
  }

  if (col_start == col_stop) {
    if (CollectFromThisColumn (col_start, only_these_columns, num_col)) {
      txt = GetFragmentsFromDocCol (doc, item, col_start, row_start, row_stop, char_start, char_stop);
      ValNodeAddPointer (&fragments, 0, txt);
    }
  } else {
    if (CollectFromThisColumn (col_start, only_these_columns, num_col)) {
      txt = GetFragmentsFromDocCol (doc, item, col_start, row_start, num_rows, char_start, -1);
      ValNodeAddPointer (&fragments, 0, txt);
    }
    col = col_start + 1;
    while (col < col_stop) {
      if (CollectFromThisColumn (col, only_these_columns, num_col)) {
        txt = GetFragmentsFromDocCol (doc, item, col, 1, num_rows, 0, -1);
        ValNodeAddPointer (&fragments, 0, txt);
      }
      col++;
    }
    if (CollectFromThisColumn (col_stop, only_these_columns, num_col)) {
      txt = GetFragmentsFromDocCol (doc, item, col_stop, 1, row_stop, 0, char_stop);
      ValNodeAddPointer (&fragments, 0, txt);
    }
  }
  txt = GetClickableTextFromItemList (fragments, "\t");
  fragments = ValNodeFreeData (fragments);

  return txt;
}


NLM_EXTERN CharPtr GetSelectedDocText (DoC doc, Int2 item_start, Int2 row_start, Int2 col_start, Int2 char_start,
                                       Int2 item_stop, Int2 row_stop, Int2 col_stop, Int2 char_stop,
                                       Int2Ptr only_these_columns, Int2 num_col)
{
  Int2             item;
  CharPtr          txt;
  ValNodePtr       fragment_list = NULL;
  
  if (doc == NULL || item_start < 1)
  {
    return NULL;
  }

  if (char_start < 0) char_start = 0;

  if (item_start == item_stop) {
    txt = GetFragmentsFromDocItem (doc, item_start, col_start, col_stop, row_start, row_stop, char_start, char_stop, only_these_columns, num_col); 
    ValNodeAddPointer (&fragment_list, 0, txt);
  } else {
    txt = GetFragmentsFromDocItem (doc, item_start, col_start, -1, row_start, -1, char_start, -1, only_these_columns, num_col);
    ValNodeAddPointer (&fragment_list, 0, txt);
    item = item_start + 1;
    while (item < item_stop) {
      txt = GetFragmentsFromDocItem (doc, item, 1, -1, 1, -1, 0, -1, only_these_columns, num_col);
      ValNodeAddPointer (&fragment_list, 0, txt);
      item++;
    }
    txt = GetFragmentsFromDocItem (doc, item, 1, col_stop, 1, row_stop, 0, char_stop, only_these_columns, num_col);
    ValNodeAddPointer (&fragment_list, 0, txt);
  }

  txt = GetClickableTextFromItemList (fragment_list, "\r\n");
  fragment_list = ValNodeFreeData (fragment_list);
  return txt;
}

extern CharPtr GetSelectedClickableListText (DialoG d)
{
  ClickableListPtr dlg;
  Int2             numRows;
  Int2             item, row, col, char_offset;
  Int4             text_len = 0, len = 0;
  CharPtr          txt, cp_txt;
  ValNodePtr       fragment_list = NULL, item_list = NULL;
  
  dlg = (ClickableListPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->text_select_item_start < 1)
  {
    return NULL;
  }

  item = dlg->text_select_item_start;
  row = dlg->text_select_row_start;
  char_offset = dlg->text_select_char_start;

  while (item <= dlg->text_select_item_stop) {
    if (item == dlg->text_select_item_start) {
      if (item == dlg->text_select_item_stop) {
        /* all text from one item */
        /* all text will be from last column in item */
        GetItemParams4 (dlg->doc, item, NULL, NULL, &col, NULL, NULL);
        if (row == dlg->text_select_row_stop) {
          /* all text from one row */
          txt = GetDocText (dlg->doc, item, row, col);
          len = StringLen (txt);
          if (dlg->text_select_char_stop >= len) {
            /* selection was drawn beyond length of text */
            dlg->text_select_char_stop = len - 1;
          }
          if (dlg->text_select_char_start >= len) {
            /* do nothing - selection is not within text */
          } else {
            cp_txt = (CharPtr) MemNew (sizeof (Char) * (2 + dlg->text_select_char_stop - dlg->text_select_char_start));
            StringNCpy (cp_txt, txt + dlg->text_select_char_start, 1 + dlg->text_select_char_stop - dlg->text_select_char_start);
            cp_txt[1 + dlg->text_select_char_stop - dlg->text_select_char_start] = 0;
            ValNodeAddPointer (&fragment_list, 0, cp_txt);
          }
          /* free txt read from Doc */
          txt = MemFree (txt);
        } else {
          /* take text from several rows */
          item_list = NULL;
          while (row < dlg->text_select_row_stop) {
            txt = GetDocText (dlg->doc, item, row, col);
            len = StringLen (txt);
            if (row == dlg->text_select_row_start && dlg->text_select_char_start >= len) {
              /* do nothing - selection is not within text */
              txt = MemFree (txt);
            } else {
              if (row == dlg->text_select_row_start && dlg->text_select_char_start > 0) {
                cp_txt = (CharPtr) MemNew (sizeof (Char) * (1 + StringLen (txt) - dlg->text_select_char_start));
                StringCpy (cp_txt, txt + dlg->text_select_char_start);
                txt = MemFree (txt);
                txt = cp_txt;
              }
              ValNodeAddPointer (&item_list, 0, txt);
            }
            row++;
          }
          txt = GetDocText (dlg->doc, item, row, col);
          if (dlg->text_select_char_stop < len) {
            txt[dlg->text_select_char_stop] = 0;
          }
          ValNodeAddPointer (&item_list, 0, txt);
          txt = GetClickableTextFromItemList (item_list, " ");
          ValNodeAddPointer (&fragment_list, 0, txt);
          item_list = ValNodeFreeData (item_list);
        }
      } else {
        /* take all text after first row and char offset */
        item_list = NULL;
        GetItemParams4 (dlg->doc, item, NULL, &numRows, &col, NULL, NULL);
        while (row <= numRows) {
          txt = GetDocText (dlg->doc, item, row, col);
          len = StringLen (txt);
          if (row == dlg->text_select_row_start && dlg->text_select_char_start >= len) {
            /* do nothing = selection is outside text */
            txt = MemFree (txt);
          } else {
            if (row == dlg->text_select_row_start && dlg->text_select_char_start > 0) {
              cp_txt = (CharPtr) MemNew (sizeof (Char) * (1 + StringLen (txt) - dlg->text_select_char_start));
              StringCpy (cp_txt, txt + dlg->text_select_char_start);
              txt = MemFree (txt);
              txt = cp_txt;
            }
            ValNodeAddPointer (&item_list, 0, txt);
          }
          row++;
        }
        txt = GetClickableTextFromItemList (item_list, " ");
        ValNodeAddPointer (&fragment_list, 0, txt);
        item_list = ValNodeFreeData (item_list);
      }
    } else if (item == dlg->text_select_item_stop) {
      /* take all text until last row and char offset */
      row = 1;
      item_list = NULL;
      GetItemParams4 (dlg->doc, item, NULL, NULL, &col, NULL, NULL);
      while (row < dlg->text_select_row_stop) {
        txt = GetDocText (dlg->doc, item, row, col);
        ValNodeAddPointer (&item_list, 0, txt);
      }
      txt = GetDocText (dlg->doc, item, row, col);
      if (dlg->text_select_char_stop + 1 < StringLen (txt)) {
        txt[dlg->text_select_char_stop + 1] = 0;
      }
      ValNodeAddPointer (&item_list, 0, txt);
      txt = GetClickableTextFromItemList (item_list, " ");
      ValNodeAddPointer (&fragment_list, 0, txt);
      item_list = ValNodeFreeData (item_list);
    } else {
      GetItemParams4 (dlg->doc, item, NULL, NULL, &col, NULL, NULL);
      txt = GetDocText (dlg->doc, item, 0, col);
      if (txt != NULL && txt[StringLen(txt) - 1] == '\n') {
        /* remove terminal carriage return */
        txt[StringLen(txt) - 1] = 0;
      }
      ValNodeAddPointer (&fragment_list, 0, txt);
      text_len += StringLen (txt) + 1;
    }
    item++;
  }
  txt = GetClickableTextFromItemList (fragment_list, "\r\n");
  fragment_list = ValNodeFreeData (fragment_list);
  return txt;
}

typedef struct basegbqualeditor {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer         callback_data;
} BaseGBQualEditor, PNTR BaseGBQualEditorPtr;

static void ChangeGBQualEditorPopup (PopuP p)
{
  BaseGBQualEditorPtr dlg;

  dlg = (BaseGBQualEditorPtr) GetObjectExtra (p);
  if (dlg != NULL && dlg->tlp_callback != NULL) {
    (dlg->tlp_callback)(dlg->callback_data);
  }
}

static void ChangeGBQualEditorButton (ButtoN b)
{
  BaseGBQualEditorPtr dlg;

  dlg = (BaseGBQualEditorPtr) GetObjectExtra (b);
  if (dlg != NULL && dlg->tlp_callback != NULL) {
    (dlg->tlp_callback)(dlg->callback_data);
  }
}

static void ChangeGBQualEditorText (TexT t)
{
  BaseGBQualEditorPtr dlg;

  dlg = (BaseGBQualEditorPtr) GetObjectExtra (t);
  if (dlg != NULL && dlg->tlp_callback != NULL) {
    (dlg->tlp_callback)(dlg->callback_data);
  }
}


typedef struct collectiondatedlg {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer callback_data;
  PopuP month;
  PopuP day;
  PopuP year;
  Int4  start_year;
} CollectionDateDlgData, PNTR CollectionDateDlgPtr;

static void PopulateDayPopup (PopuP p, Int4 month);

static void PointerToCollectionDateDialog (DialoG d, Pointer data)
{
  CollectionDateDlgPtr dlg;
  CharPtr              val, reformatted;
  Boolean              ambiguous = FALSE;
  CharPtr              cp, cp2, month_abbrev;
  Int4                 year = 1, month = 1, day = 1;

  dlg = (CollectionDateDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  SetValue (dlg->year, 1);
  SetValue (dlg->month, 1);
  SetValue (dlg->day, 1);
  Disable (dlg->month);
  Disable (dlg->day);

  val = (CharPtr) data;

  reformatted = ReformatDateStringEx (val, TRUE, &ambiguous);
  if (StringHasNoText (reformatted) || ambiguous) {
    /* do nothing */
  } else {
    cp = StringChr (reformatted, '-');
    if (cp == NULL) {
      year = GetYearFromToken (reformatted, StringLen (reformatted));
      year = year - dlg->start_year + 2;
    } else {
      if (isdigit (*reformatted)) {
        day = ReadNumberFromToken (reformatted, cp - reformatted);
        day += 1;
        cp++;
        cp2 = StringChr (cp, '-');
        month_abbrev = GetMonthFromToken (cp, cp2 - cp);
        month = GetMonthNumFromAbbrev (month_abbrev);
        if (month > -1) {
          month += 2;
        } else {
          month = 1;
        }
        year = GetYearFromToken (cp2 + 1, StringLen (cp2 + 1));
        year = year - dlg->start_year + 2;
      }
      else
      {
        month_abbrev = GetMonthFromToken (reformatted, cp - reformatted);
        month = GetMonthNumFromAbbrev (month_abbrev);
        if (month > -1) {
          month += 2;
        } else {
          month = 1;
        }
        year = GetYearFromToken (cp + 1, StringLen (cp + 1));
        year = year - dlg->start_year + 2;
      }
    }
    SetValue (dlg->year, year);
    Enable (dlg->month);
    SetValue (dlg->month, month);
    if (month > 1) {
      PopulateDayPopup (dlg->day, month);
      Enable (dlg->day);
    }
    SetValue (dlg->day, day);
  }  
}

static Pointer CollectionDateDialogToPointer (DialoG d)
{
  CollectionDateDlgPtr dlg;
  Int4                 year, month = -1, day = 0;
  CharPtr              year_fmt = "%d";
  CharPtr              mon_year_fmt = "%s-%d";
  CharPtr              day_mon_year_fmt = "%d-%s-%d";
  Char                 date_str[100];

  dlg = (CollectionDateDlgPtr) GetObjectExtra (d);
  date_str[0] = 0;
  if (dlg != NULL) {
    year = GetValue (dlg->year);
    if (year < 2) {
      return NULL;
    } else {
      year = year + dlg->start_year - 2;
      month = GetValue (dlg->month);
      if (month < 2) {
        sprintf (date_str, year_fmt, year);
      } else {
        day = GetValue (dlg->day);
        if (day < 2) {
          sprintf (date_str, mon_year_fmt, GetMonthAbbrev (month - 1), year);
        } else {
          sprintf (date_str, day_mon_year_fmt, day - 1, GetMonthAbbrev (month - 1), year);
        }
      }
    }
  }

  return StringSave (date_str);
}

static void PopulateDayPopup (PopuP p, Int4 month)
{
  Int4 i;
  Char day[10];

  Reset (p);
  PopupItem (p, "");

  if (month > 1) {
    for (i = 1; i <= GetDaysInMonth(month - 1); i++) {
      sprintf (day, "%d", i);
      PopupItem (p, day);
    }
  }
}

static void PopulateMonthPopup (PopuP p)
{
  Int4 i;

  PopupItem (p, "");
  for (i = 0; i < 12; i++) {
    PopupItem (p, GetMonthAbbrev(i + 1));
  }
}

static void ChangeCollectionDateMonth (PopuP p)
{
  CollectionDateDlgPtr dlg;
  Int4                 month;

  dlg = (CollectionDateDlgPtr) GetObjectExtra (p);
  if (dlg == NULL) return;

  month = GetValue (p);
  if (month < 2) {
    Disable (dlg->day);
  } else {
    PopulateDayPopup (dlg->day, month);
    Enable (dlg->day);
  }
  if (dlg->tlp_callback != NULL) {
    (dlg->tlp_callback) (dlg->callback_data);
  }
}

static Int4 PopulateYearPopup (PopuP p)
{
    Nlm_DayTime dt;
  Char        year_str[20];
  Int4        start_year, i, end_year;

  GetDayTime (&dt);
  start_year = dt.tm_year + 1901 - 10;
  end_year = start_year + 20;
  PopupItem (p, " ");
  for (i=start_year; i <= end_year; i++) {
    sprintf (year_str, "%d", i);
    PopupItem (p, year_str);
  }
  return start_year;
}

static void ChangeCollectionDateYear (PopuP p)
{
  CollectionDateDlgPtr dlg;

  dlg = (CollectionDateDlgPtr) GetObjectExtra (p);
  if (dlg == NULL) return;

  if (GetValue (p) < 2) {
    Disable (dlg->month);
    Disable (dlg->day);
  } else {
    Enable (dlg->month);
    ChangeCollectionDateMonth (dlg->month);
  }
  if (dlg->tlp_callback != NULL) {
    (dlg->tlp_callback) (dlg->callback_data);
  }
}

extern DialoG CollectionDateDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                               TaglistCallback tlp_callback,
                               Pointer callback_data)
{
  CollectionDateDlgPtr dlg;
  GrouP           p;

  p = HiddenGroup (h, 4, 0, NULL);
  dlg = (CollectionDateDlgPtr) MemNew (sizeof(CollectionDateDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = PointerToCollectionDateDialog;
  dlg->fromdialog = CollectionDateDialogToPointer;
  dlg->testdialog = NULL;
  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;

  dlg->year = PopupList (p, TRUE, ChangeCollectionDateYear);
  SetObjectExtra (dlg->year, dlg, NULL);
  dlg->start_year = PopulateYearPopup (dlg->year);
  SetValue (dlg->year, 1);

  dlg->month = PopupList (p, TRUE, ChangeCollectionDateMonth);
  SetObjectExtra (dlg->month, dlg, NULL);
  PopulateMonthPopup (dlg->month);
  SetValue (dlg->month, 1);

  dlg->day = PopupList (p, TRUE, ChangeGBQualEditorPopup);
  SetObjectExtra (dlg->day, dlg, NULL);

  return (DialoG) p;
}

extern Boolean ParseCollectionDateOk (CharPtr txt)
{
  CharPtr date_str;
  Boolean ambiguous = FALSE;
  Boolean rval = FALSE;

  if (StringHasNoText (txt)) {
    rval = TRUE;
  } else {
    date_str = ReformatDateStringEx (txt, TRUE, &ambiguous);
    if (date_str != NULL && !ambiguous) {
      rval = TRUE;
    } 
    date_str = MemFree (date_str);
  }
  return rval;
}

typedef struct rptunitrangedlg {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer callback_data;
  TexT  range_start;
  TexT  range_stop;
} RptUnitRangeDlgData, PNTR RptUnitRangeDlgPtr;

static Boolean ParseRptUnitRangeOkEx (CharPtr txt, Int4Ptr pstart, Int4Ptr pstop)
{
  CharPtr cp;
  Int4    start = -1, stop = -1;
  Char    ch;
  Boolean rval = FALSE;

  if (StringHasNoText (txt)) {
    return TRUE;
  }
  
  cp = txt;
  while (*cp != 0 && isdigit (*cp)) {
    cp++;
  }
  if (cp != txt) {
    ch = *cp;
    *cp = 0;
    start = atoi (txt);
    *cp = ch;
    cp++;
    while (*cp == ch) {
      cp++;
    }
    txt = cp;
    while (isdigit (*cp)) {
      cp++;
    }
    if (cp != txt) {
      stop = atoi (txt);
    }
  }

  if (start > -1 && stop > -1) {
    if (pstart != NULL) {
      *pstart = start;
    }
    if (pstop != NULL) {
      *pstop = stop;
    }
    rval = TRUE;
  }
  return rval;
}

static void StringToRptUnitRangeDialog (DialoG d, Pointer data)
{
  RptUnitRangeDlgPtr dlg;
  CharPtr            val;
  Int4               start = -1, stop = -1;
  Char               num_str[100];

  dlg = (RptUnitRangeDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  val = (CharPtr) data;
  
  ParseRptUnitRangeOkEx (val, &start, &stop);
  if (start > -1 && stop > -1) {
    sprintf (num_str, "%d", start);
    SetTitle (dlg->range_start, num_str);
    sprintf (num_str, "%d", stop);
    SetTitle (dlg->range_stop, num_str);
  } else {
    SetTitle (dlg->range_start, "");
    SetTitle (dlg->range_stop, "");
  }
}

static Pointer RptUnitRangeDialogToString (DialoG d)
{
  RptUnitRangeDlgPtr dlg;
  CharPtr            val = NULL;
  CharPtr            start, stop;

  dlg = (RptUnitRangeDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;

  start = SaveStringFromText (dlg->range_start);
  stop = SaveStringFromText (dlg->range_stop);

  if (StringHasNoText (start) && StringHasNoText (stop)) {
    return StringSave ("");
  } else {
    val = (CharPtr) MemNew (sizeof (Char) * (StringLen (start) + StringLen (stop) + 3));
    sprintf (val, "%s..%s", start == NULL ? "" : start, stop == NULL ? "" : stop);
  }
  start = MemFree (start);
  stop = MemFree (stop);
  return val;
}


extern DialoG CreateRptUnitRangeDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                         TaglistCallback tlp_callback,
                                         Pointer callback_data)
{
  RptUnitRangeDlgPtr dlg;
  GrouP           p;

  p = HiddenGroup (h, 6, 0, NULL);
  dlg = (RptUnitRangeDlgPtr) MemNew (sizeof(RptUnitRangeDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToRptUnitRangeDialog;
  dlg->fromdialog = RptUnitRangeDialogToString;
  dlg->testdialog = NULL;

  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;

  StaticPrompt (p, "Start", 0, dialogTextHeight, programFont, 'l');
  dlg->range_start = DialogText (p, "", 5, ChangeGBQualEditorText);
  SetObjectExtra (dlg->range_start, dlg, NULL);
  StaticPrompt (p, "Stop", 0, dialogTextHeight, programFont, 'l');
  dlg->range_stop = DialogText (p, "", 5, ChangeGBQualEditorText);
  SetObjectExtra (dlg->range_stop, dlg, NULL);


  return (DialoG) p;
}

static Boolean ParseRptUnitRangeOk (CharPtr txt)
{
  return ParseRptUnitRangeOkEx (txt, NULL, NULL);
}


typedef struct controlplusfreedlg {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer callback_data;
  PopuP element_type;
  TexT  description;

  CharPtr PNTR control_words;
} ControlPlusFreeDlgData, PNTR ControlPlusFreeDlgPtr;


static Int2 GetControlNum (CharPtr txt, CharPtr PNTR control_words, CharPtr PNTR desc_start)
{
  CharPtr cp;
  Int2    i;
  Int4    keyword_len;

  if (desc_start != NULL) {
    *desc_start = NULL;
  }
  if (StringHasNoText (txt) || control_words == NULL) {
    return 0;
  }
  /* skip over any leading spaces */
  while (isspace (*txt)) {
    txt++;
  }
  cp = StringChr (txt, ':');
  if (cp == NULL) {
    for (i = 1; control_words[i] != NULL; i++) {
      if (StringICmp (txt, control_words[i]) == 0) {
        return i;
      }
    }
    return -1;
  } else {
    keyword_len = cp - txt;
    while (keyword_len > 0 && isspace (txt[keyword_len - 1])) {
      keyword_len--;
    }
    if (keyword_len == 0) {
      return 0;
    }
    for (i = 1; control_words[i] != NULL; i++) {
      if (StringNICmp (txt, control_words[i], keyword_len) == 0) {
        if (desc_start != NULL && !StringHasNoText (cp + 1)) {
          *desc_start = cp + 1;
          while (isspace(**desc_start)) {
            (*desc_start)++;
          }
        }
        return i;
      }
    }
  }
  return -1;
}

static void ChangeControlPlusFreeType (PopuP p)
{
  ControlPlusFreeDlgPtr dlg;

  dlg = (ControlPlusFreeDlgPtr) GetObjectExtra (p);
  if (dlg == NULL) return;

  if (GetValue (dlg->element_type) <= 1) {
    Disable (dlg->description);
  } else {
    Enable (dlg->description);
  }
  ChangeGBQualEditorPopup (p);
}

static void StringToControlPlusFreeDialog (DialoG d, Pointer data)
{
  ControlPlusFreeDlgPtr dlg;
  CharPtr             val;
  CharPtr             desc_start = NULL;
  Int2                num;

  dlg = (ControlPlusFreeDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  val = (CharPtr) data;

  num = GetControlNum (val, dlg->control_words, &desc_start);

  if (num >= 0) {
    SetValue (dlg->element_type, num + 1);
    SetTitle (dlg->description, desc_start == NULL ? "" : desc_start);
  }
}

static Pointer ControlPlusFreeDialogToString (DialoG d)
{
  ControlPlusFreeDlgPtr dlg;
  CharPtr             val = NULL;
  CharPtr             desc_start = NULL;
  Int2                num;

  dlg = (ControlPlusFreeDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;

  num = GetValue (dlg->element_type);

  if (num >= 1) {
    desc_start = SaveStringFromText (dlg->description);
    if (StringHasNoText (desc_start)) {
      val = StringSave (dlg->control_words[num - 1]);
    } else {
      val = (CharPtr) MemNew (sizeof (Char) * (StringLen (dlg->control_words[num - 1]) + StringLen (desc_start) + 2));
      sprintf (val, "%s:%s", dlg->control_words[num - 1], desc_start);
    }
  }
  return (Pointer) val;
}


static DialoG CreateControlPlusFreeDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                           TaglistCallback tlp_callback,
                                           Pointer callback_data,
                                           CharPtr PNTR control_words)
{
  ControlPlusFreeDlgPtr dlg;
  GrouP           p;
  Int2            i;

  p = HiddenGroup (h, 3, 0, NULL);
  dlg = (ControlPlusFreeDlgPtr) MemNew (sizeof(ControlPlusFreeDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToControlPlusFreeDialog;
  dlg->fromdialog = ControlPlusFreeDialogToString;
  dlg->testdialog = NULL;

  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;

  dlg->control_words = control_words;
  dlg->element_type = PopupList (p, TRUE, ChangeControlPlusFreeType);
  for (i = 0; dlg->control_words[i] != NULL; i++) {
    PopupItem (dlg->element_type, dlg->control_words[i]);
  }
  SetValue (dlg->element_type, 1);
  SetObjectExtra (dlg->element_type, dlg, NULL);

  dlg->description = DialogText (p, "", 15, ChangeGBQualEditorText);
  SetObjectExtra (dlg->description, dlg, NULL);

  return (DialoG) p;
}

static void CopyTextToControlPlusFreeDialog (DialoG d, CharPtr txt)
{
  ControlPlusFreeDlgPtr dlg;

  dlg = (ControlPlusFreeDlgPtr) GetObjectExtra (d);

  if (dlg == NULL || StringHasNoText (txt)) return;

  SetTitle (dlg->description, txt);
}


CharPtr mobile_element_keywords[] = 
{ " ",
  "transposon",
  "retrotransposon",
  "integron",
  "insertion sequence", 
  "non-LTR retrotransposon",              
  "SINE", 
  "MITE", 
  "LINE", 
  "other",
  NULL};


static DialoG CreateMobileElementDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                         TaglistCallback tlp_callback,
                                         Pointer callback_data)
{
  return CreateControlPlusFreeDialog (h, sep, name, tlp_callback, callback_data, mobile_element_keywords);
}

static Boolean ParseMobileElementOk (CharPtr txt)
{

  if (GetControlNum (txt, mobile_element_keywords, NULL) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}


CharPtr regulatory_class_keywords[] = 
{ " ",
  "attenuator",
  "CAAT_signal",
  "DNase_I_hypersensitive_site",
  "enhancer_blocking_element",
  "enhancer",
  "GC_signal",
  "imprinting_control_region",
  "insulator",
  "locus_control_region",
  "matrix_attachment_region",
  "minus_10_signal",
  "minus_35_signal",
  "polyA_signal_sequence",
  "promoter",
  "recoding_stimulatory_region",
  "replication_regulatory_region",
  "response_element",
  "ribosome_binding_site",
  "riboswitch",
  "silencer",
  "TATA_box",
  "terminator",
  "transcriptional_cis_regulatory_region",
  "other",
  NULL};


static DialoG CreateRegulatoryClassDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                           TaglistCallback tlp_callback,
                                           Pointer callback_data)
{
  return CreateControlPlusFreeDialog (h, sep, name, tlp_callback, callback_data, regulatory_class_keywords);
}

static Boolean ParseRegulatoryClassOk (CharPtr txt)
{

  if (GetControlNum (txt, regulatory_class_keywords, NULL) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

CharPtr satellite_keywords[] = 
{ " ",
  "satellite",
  "microsatellite",
  "minisatellite",
  NULL};


static DialoG CreateSatelliteDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                         TaglistCallback tlp_callback,
                                         Pointer callback_data)
{
  return CreateControlPlusFreeDialog (h, sep, name, tlp_callback, callback_data, satellite_keywords);
}

static Boolean ParseSatelliteOk (CharPtr txt)
{

  if (GetControlNum (txt, satellite_keywords, NULL) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

CharPtr recombination_class_keywords[] = 
{ " ",
  "meiotic_recombination",
  "mitotic_recombination",
  "non_allelic_homologous_recombination",
  "chromosome_breakpoint",
  "other",
  NULL};


static DialoG CreateRecombClassDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                       TaglistCallback tlp_callback,
                                       Pointer callback_data)
{
  return CreateControlPlusFreeDialog (h, sep, name, tlp_callback, callback_data, recombination_class_keywords);
}

static Boolean ParseRecombClassOk (CharPtr txt)
{

  if (GetControlNum (txt, recombination_class_keywords, NULL) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

typedef struct truefalsedlg {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer callback_data;
  ButtoN is_true;
} TrueFalseDlgData, PNTR TrueFalseDlgPtr;

static void PointerToTrueFalseDialog (DialoG d, Pointer data)
{
  TrueFalseDlgPtr dlg;
  CharPtr         val;

  dlg = (TrueFalseDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  val = (CharPtr) data;

  if (StringICmp (val, "TRUE") == 0) {
    SetStatus (dlg->is_true, TRUE);
  } else {
    SetStatus (dlg->is_true, FALSE);
  }
}

static Pointer TrueFalseDialogToPointer (DialoG d)
{
  TrueFalseDlgPtr dlg;

  dlg = (TrueFalseDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return StringSave ("FALSE");

  if (GetStatus (dlg->is_true)) {
    return StringSave ("TRUE");
  } else {
    return StringSave ("FALSE");
  }
}

static DialoG TrueFalseDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                               TaglistCallback tlp_callback,
                               Pointer callback_data)
{
  TrueFalseDlgPtr dlg;
  GrouP           p;

  p = HiddenGroup (h, 2, 0, NULL);
  dlg = (TrueFalseDlgPtr) MemNew (sizeof(TrueFalseDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = PointerToTrueFalseDialog;
  dlg->fromdialog = TrueFalseDialogToPointer;
  dlg->testdialog = NULL;
  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;

  dlg->is_true = CheckBox (p, "", ChangeGBQualEditorButton);
  SetObjectExtra (dlg->is_true, dlg, NULL);

  return (DialoG) p;
}

static Boolean ParseTrueFalseOk (CharPtr txt)
{
  if (StringHasNoText (txt) || StringICmp (txt, "TRUE") == 0 || StringICmp (txt, "FALSE") == 0) {
    return TRUE;
  } else {
    return FALSE;
  }
}

typedef struct simplelistqualdlg {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer callback_data;
  PopuP list;
  CharPtr PNTR val_list;
} SimpleListQualDlgData, PNTR SimpleListQualDlgPtr;

static Int2 GetListNum (CharPtr val, CharPtr PNTR val_list)
{
  Int2 i;
  if (StringHasNoText (val)) return 0;
  for (i = 1; val_list[i] != NULL; i++) {
    if (StringICmp (val, val_list[i]) == 0) {
      return i;
    }
  }
  return -1;
}

static void StringToSimpleListQualDialog (DialoG d, Pointer data)
{
  SimpleListQualDlgPtr dlg;
  CharPtr              val;
  Int2                 num;

  dlg = (SimpleListQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  val = (CharPtr) data;
  num = GetListNum (val, dlg->val_list);
  if (num > -1) {
    SetValue (dlg->list, num + 1);
  } else {
    SetValue (dlg->list, 1);
  }
}

static Pointer SimpleListQualDialogToString (DialoG d)
{
  SimpleListQualDlgPtr dlg;
  CharPtr              val = NULL;
  Int2                 num;

  dlg = (SimpleListQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;

  num = GetValue (dlg->list);
  if (num > 1) {
    val = StringSave (dlg->val_list[num - 1]);
  }
  return (Pointer) val;
}

static DialoG SimpleListQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name, CharPtr PNTR list,
                                    TaglistCallback tlp_callback,
                                    Pointer callback_data)
{
  SimpleListQualDlgPtr dlg;
  GrouP           p;
  Int2            i;

  p = HiddenGroup (h, 2, 0, NULL);
  dlg = (SimpleListQualDlgPtr) MemNew (sizeof(SimpleListQualDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToSimpleListQualDialog;
  dlg->fromdialog = SimpleListQualDialogToString;
  dlg->testdialog = NULL;
  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;

  dlg->list = PopupList (p, TRUE, ChangeGBQualEditorPopup);
  SetObjectExtra (dlg->list, dlg, NULL);
  dlg->val_list = list;
  if (dlg->val_list != NULL) {
    for (i = 0; dlg->val_list[i] != NULL; i++) {
      PopupItem (dlg->list, dlg->val_list[i]);
    }
    SetValue (dlg->list, 1);
  }
  return (DialoG) p;
}

typedef struct multilistqualdlg {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback; 
  Pointer callback_data;
  ButtoN  *button_list;
  TexT    last_txt;

  CharPtr PNTR val_list;
} MultiListQualDlgData, PNTR MultiListQualDlgPtr;

static void CleanupMultiListQualDlg (GraphiC g, VoidPtr data)

{
  MultiListQualDlgPtr  dlg;

  dlg = (MultiListQualDlgPtr) data;
  if (dlg != NULL) {
    dlg->button_list = MemFree (dlg->button_list);
  }
  StdCleanupExtraProc (g, data);
}

static void CheckMultiListLast (ButtoN b)
{
  MultiListQualDlgPtr dlg;

  dlg = (MultiListQualDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  if (GetStatus (b)) {
    Enable (dlg->last_txt);
  } else {
    Disable (dlg->last_txt);
  }
  if (dlg->tlp_callback) {
    (dlg->tlp_callback) (dlg->callback_data);
  }
}

static void ApplyOneValToMultiListQualDialog (MultiListQualDlgPtr dlg, CharPtr val, CharPtr PNTR other_text)
{
  Int2    i;
  CharPtr tmp;

  if (dlg == NULL || StringHasNoText (val) || other_text == NULL) return;

  i = GetListNum (val, dlg->val_list);
  if (i > 0) {
    SetStatus (dlg->button_list[i - 1], TRUE);
  } else {
    if (*other_text == NULL) {
      *other_text = StringSave (val);
    } else {
      tmp = (CharPtr) MemNew (sizeof (Char) * (StringLen (*other_text) + StringLen (val) + 2));
      sprintf (tmp, "%s,%s", *other_text, val);
      *other_text = MemFree (*other_text);
      *other_text = tmp;
    }
  }
}

static void StringToMultiListQualDialog (DialoG d, Pointer data)
{
  MultiListQualDlgPtr dlg;
  CharPtr             val, cp, other_text = NULL;
  Int2                i;

  dlg = (MultiListQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  for (i = 1; dlg->val_list[i] != NULL; i++) {
    SetStatus (dlg->button_list[i - 1], FALSE);
  }
  SetStatus (dlg->button_list[i - 1], FALSE);
  SetTitle (dlg->last_txt, "");

  val = (CharPtr) data;
  if (StringHasNoText (val)) {
    return;
  }
  
  if (*val == '(' && val[StringLen(val) - 1] == ')') {
    /* parentheses list */
    val++;
    cp = StringChr (val, ',');
    while (cp != NULL) {
      *cp = 0;
      ApplyOneValToMultiListQualDialog (dlg, val, &other_text);
      *cp = ',';
      val = cp + 1;
      while (isspace(*val)) {
        val++;
      }
      cp = StringChr (val, ',');
    }
    val[StringLen(val) - 1] = 0;
    ApplyOneValToMultiListQualDialog (dlg, val, &other_text);
    val[StringLen(val) - 1] = ')';
  } else {
    ApplyOneValToMultiListQualDialog (dlg, val, &other_text);
  }
  if (dlg->last_txt != NULL)
  {
    if (!StringHasNoText (other_text)) {
      SetTitle (dlg->last_txt, other_text);
      SetStatus (dlg->button_list[i - 1], TRUE);
      Enable (dlg->last_txt);
    } else {
      Disable (dlg->last_txt);
    }
  }
  other_text = MemFree (other_text);
}

static Pointer MultiListQualDialogToString (DialoG d)
{
  MultiListQualDlgPtr dlg;
  CharPtr             val = NULL, tmp;
  Int2                i;

  dlg = (MultiListQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;
  for (i = 1; dlg->val_list[i] != NULL; i++) {
    if (GetStatus (dlg->button_list[i - 1])) {
      if (val == NULL) {
        val = StringSave (dlg->val_list[i]);
      } else {
        tmp = StringSave (dlg->val_list[i]);
        val = CombineSplitGBQual (val, tmp);
      }
    }
  }
  if (dlg->last_txt != NULL
      && GetStatus (dlg->button_list[i - 1]) 
      && !TextHasNoText (dlg->last_txt)) {
    if (val == NULL) {
      val = SaveStringFromText (dlg->last_txt);
    } else {
      tmp = SaveStringFromText (dlg->last_txt);
      val = CombineSplitGBQual (val, tmp);
      tmp = MemFree (tmp);
    }
  }
  return val;
}

static DialoG MultiListQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name, CharPtr PNTR list,
                                   Boolean allow_not_in_list,
                                   TaglistCallback tlp_callback,
                                   Pointer callback_data)
{
  MultiListQualDlgPtr dlg;
  GrouP           p, g, last_button = NULL;
  Int2            i, num_buttons = 1; /* start with one, for the type-in wildcard value */

  p = HiddenGroup (h, -1, 0, NULL);
  dlg = (MultiListQualDlgPtr) MemNew (sizeof(MultiListQualDlgData));

  SetObjectExtra (p, dlg, CleanupMultiListQualDlg);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToMultiListQualDialog;
  dlg->fromdialog = MultiListQualDialogToString;
  dlg->testdialog = NULL;
  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;
  dlg->val_list = list;

  /* start with 1, skip blank at start of list */
  for (i = 1; dlg->val_list[i] != NULL; i++) {
    num_buttons ++;
  }

  dlg->button_list = (ButtoN *) MemNew (num_buttons * sizeof (ButtoN));

  g = HiddenGroup (p, 4, 0, NULL);
  for (i = 1; dlg->val_list [i] != NULL; i++) {
    dlg->button_list[i - 1] = CheckBox (g, dlg->val_list[i], ChangeGBQualEditorButton);
    SetObjectExtra (dlg->button_list[i - 1], dlg, NULL);
  }
  if (allow_not_in_list)
  {
    last_button = HiddenGroup (p, 2, 0, NULL);
    dlg->button_list[i - 1] = CheckBox (last_button, "", CheckMultiListLast);
    SetObjectExtra (dlg->button_list[i - 1], dlg, NULL);
    dlg->last_txt = DialogText (last_button, "", 20, ChangeGBQualEditorText);
    SetObjectExtra (dlg->last_txt, dlg, NULL);
    Disable (dlg->last_txt);
  }

  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) last_button, NULL);
    
  return (DialoG) p;
}

static Boolean ParseMultiListQualOk (CharPtr val)
{
  return TRUE;
}

CharPtr codon_start_values[] = {" ", "1", "2", "3", NULL};
static DialoG CodonStartQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                    TaglistCallback tlp_callback,
                                    Pointer callback_data)

{
  return SimpleListQualDialog (h, sep, name, codon_start_values, tlp_callback, callback_data);
}
static Boolean ParseCodonStartOk (CharPtr val)
{
  if (GetListNum (val, codon_start_values) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

CharPtr mol_type_values[] = {" ", "genomic DNA", "genomic RNA", "mRNA", "tRNA", "rRNA",
                "snoRNA", "snRNA", "scRNA", "pre-RNA", "other RNA",
                "other DNA", "viral cRNA", "unassigned DNA", "unassigned RNA", NULL};
static DialoG MolTypeQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                 TaglistCallback tlp_callback,
                                 Pointer callback_data)
{
  return SimpleListQualDialog (h, sep, name, mol_type_values, tlp_callback, callback_data);
}
static Boolean ParseMolTypeOk (CharPtr val)
{
  if (GetListNum (val, mol_type_values) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

CharPtr organelle_values[] = {" ", "mitochondrion", "nucleomorph", "plastid",
                              "mitochondrion:kinetoplast",
                              "plastid:chloroplast", "plastid:apicoplast", 
                              "plastid:chromoplast", "plastid:cyanelle", 
                              "plastid:leucoplast", "plastid:proplastid", NULL};
static DialoG OrganelleQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                   TaglistCallback tlp_callback,
                                   Pointer callback_data)
{
  return SimpleListQualDialog (h, sep, name, organelle_values, tlp_callback, callback_data);
}
static Boolean ParseOrganelleOk (CharPtr val)
{
  if (GetListNum (val, organelle_values) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

CharPtr rpt_type_values[] = {
  " ",
  "tandem",
  "inverted",
  "flanking",
  "nested",
  "terminal",
  "direct",
  "dispersed",
  "long_terminal_repeat",
  "non_LTR_retrotransposon_polymeric_tract",
  "X_element_combinatorial_repeat",
  "Y_prime_element",
  "telomeric_repeat",
  "centromeric_repeat",
  "engineered_foreign_repetitive_element",
  "other",
  NULL
};
static DialoG RptTypeQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                 TaglistCallback tlp_callback,
                                 Pointer callback_data)
{


  return MultiListQualDialog (h, sep, name, rpt_type_values, FALSE, tlp_callback, callback_data);
}

static Boolean ParseRptTypeOk (CharPtr val)
{
  CharPtr cpy, cp, check;
  Boolean rval = TRUE;

  if (StringHasNoText (val))
  {
    return TRUE;
  }
  else if (val[0] == '(' && val[StringLen(val) - 1] == ')')
  {
    cpy = StringSave (val + 1);
    cpy [StringLen (cpy) - 1] = 0;
    check = cpy;
    cp = StringChr (cpy, ',');
    while (cp != NULL && rval)
    {
      *cp = 0;
      TrimSpacesAroundString (check);
      if (GetListNum (check, rpt_type_values) < 0)
      {
        rval = FALSE;
      }
      check = cp + 1;
      cp = StringChr (check, ',');
    }
    if (rval)
    {
      TrimSpacesAroundString (check);
      if (GetListNum (check, rpt_type_values) < 0)
      {
        rval = FALSE;
      }
    }
    cpy = MemFree (cpy);
  }
  else if (GetListNum (val, rpt_type_values) < 0)
  {
    rval = FALSE;
  }
  return rval;
}

CharPtr direction_values[] = {" ", "LEFT", "RIGHT", "BOTH", NULL};
static DialoG DirectionQualDialog (GrouP h, SeqEntryPtr sep, CharPtr name,
                                 TaglistCallback tlp_callback,
                                 Pointer callback_data)
{
  return SimpleListQualDialog (h, sep, name, direction_values, tlp_callback, callback_data);
}
static Boolean ParseDirectionOk (CharPtr val)
{
  if (GetListNum (val, direction_values) > -1) {
    return TRUE;
  } else {
    return FALSE;
  }
}

typedef DialoG (*BuildQualEditorDialog) PROTO ((GrouP, SeqEntryPtr, CharPtr, 
                                                TaglistCallback, Pointer));
typedef void (*CopyTextToEditor) PROTO ((DialoG, CharPtr));

typedef struct gbqualeditlist {
  CharPtr               name;
  BuildQualEditorDialog build_dlg;
  ParseOK               parse_ok;
  CopyTextToEditor      copy_txt;
} GBQualEditListData, PNTR GBQualEditListPtr;

GBQualEditListData gbqual_edit_list[] = {
  {"chloroplast",              TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"chromoplast",              TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"codon_start",              CodonStartQualDialog,        ParseCodonStartOk,       NULL },
  {"collection_date",          CollectionDateDialog,        ParseCollectionDateOk,   NULL },
  {"cyanelle",                 TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"direction",                DirectionQualDialog,         ParseDirectionOk,        NULL },
  {"germline",                 TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"kinetoplast",              TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"macronuclear",             TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"mitochondrion",            TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"mobile_element",           CreateMobileElementDialog,   ParseMobileElementOk,    CopyTextToControlPlusFreeDialog },
  {"mobile_element_type",      CreateMobileElementDialog,   ParseMobileElementOk,    CopyTextToControlPlusFreeDialog },
  {"mol_type",                 MolTypeQualDialog,           ParseMolTypeOk,          NULL },
  {"organelle",                OrganelleQualDialog,         ParseOrganelleOk,        NULL },
  {"partial",                  TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"proviral",                 TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"pseudo",                   TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"rearranged",               TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"rpt_type",                 RptTypeQualDialog,           ParseRptTypeOk,          NULL },
  {"rpt_unit_range",           CreateRptUnitRangeDialog,    ParseRptUnitRangeOk,     NULL } ,
  {"satellite",                CreateSatelliteDialog,       ParseSatelliteOk,        CopyTextToControlPlusFreeDialog },
  {"virion",                   TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"focus",                    TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"transgenic",               TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"environmental_sample",     TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"ribosomal_slippage",       TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"trans_splicing",           TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"metagenomic",              TrueFalseDialog,             ParseTrueFalseOk,        NULL },
  {"regulatory_class",         CreateRegulatoryClassDialog, ParseRegulatoryClassOk,  CopyTextToControlPlusFreeDialog },
  {"regulatory_class_type",    CreateRegulatoryClassDialog, ParseRegulatoryClassOk,  CopyTextToControlPlusFreeDialog },
  {"recombination_class",      CreateRecombClassDialog,     ParseRecombClassOk,      CopyTextToControlPlusFreeDialog },
  {"recombination_class_type", CreateRecombClassDialog,     ParseRecombClassOk,      CopyTextToControlPlusFreeDialog },
  {NULL, NULL, NULL}};


typedef struct singlegbqualedit {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback tlp_callback;
  Pointer callback_data;
  DialoG editor;
  TexT   txt;
  GrouP  choice_grp;
  ButtoN            use_editor;
  ButtoN            use_text;
  ButtoN            copy_text;
  GBQualEditListPtr edit_list;
  CharPtr           name;
  Boolean           force_string;
} SingleGBQualEditData, PNTR SingleGBQualEditPtr;

static void ChangeSingleQualEdit (GrouP g)
{
  SingleGBQualEditPtr dlg;

  dlg = (SingleGBQualEditPtr) GetObjectExtra (g);
  if (dlg != NULL) {
    if (GetValue (dlg->choice_grp) == 1) {
      SafeEnable (dlg->editor);
      Disable (dlg->txt);
    } else {
      SetValue (dlg->choice_grp, 2);
      SafeDisable (dlg->editor);
      Enable (dlg->txt);
    }
    if (dlg->tlp_callback != NULL) {
      (dlg->tlp_callback) (dlg->callback_data);
    }
  }
}

static void StringToSingleGBQualEditDialog (DialoG d, Pointer data)
{
  SingleGBQualEditPtr dlg;
  CharPtr             val;

  dlg = (SingleGBQualEditPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  val = (CharPtr) data;

  if (StringHasNoText (val) || (!dlg->force_string && dlg->edit_list != NULL && (dlg->edit_list->parse_ok(val)))) {
    SetTitle (dlg->txt, "");
    if (dlg->choice_grp != NULL) {
      SetValue (dlg->choice_grp, 1);
      Hide (dlg->use_editor);
      Hide (dlg->use_text);
      Hide (dlg->txt);
      Hide (dlg->copy_text);
      PointerToDialog (dlg->editor, val);
      Enable (dlg->editor);
      Disable (dlg->txt);
    }
  } else {
    SetTitle (dlg->txt, val);
    if (dlg->choice_grp != NULL) {
      SetValue (dlg->choice_grp, 2);
      Show (dlg->use_editor);
      Show (dlg->use_text);
      Show (dlg->txt);
      Show (dlg->copy_text);
      PointerToDialog (dlg->editor, NULL);
      Enable (dlg->txt);
      Disable (dlg->editor);
    }
  }
  if (dlg->tlp_callback != NULL) {
    (dlg->tlp_callback)(dlg->callback_data);
  }
}

static Pointer SingleGBQualEditDialogToString (DialoG d)
{
  SingleGBQualEditPtr dlg;
  CharPtr             val;

  dlg = (SingleGBQualEditPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;
  if (dlg->choice_grp == NULL || GetValue (dlg->choice_grp) == 2) {
    val = SaveStringFromText (dlg->txt);
  } else {
    val = DialogToPointer (dlg->editor);
  }
  
  return val;
}

static void GBQualEditDialogMessage (DialoG d, Int2 mssg)

{
  SingleGBQualEditPtr dlg;

  dlg = (SingleGBQualEditPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (mssg == VIB_MSG_INIT 
        || mssg == VIB_MSG_ENTER) {
      if (dlg->choice_grp == NULL || GetValue (dlg->choice_grp) == 2) {
        Select (dlg->txt);
      } else {
        Select (dlg->editor);
      }
    }
  }
}

static void GBQualEditDialogCopyText (ButtoN b)
{
  SingleGBQualEditPtr dlg;
  CharPtr             txt;

  dlg = (SingleGBQualEditPtr) GetObjectExtra (b);
  if (dlg != NULL && dlg->edit_list != NULL && dlg->edit_list->copy_txt != NULL) {
    txt = SaveStringFromText (dlg->txt);
    (dlg->edit_list->copy_txt)(dlg->editor, txt);
    txt = MemFree (txt);
  }
}

static DialoG CreateSingleGBQualEditDialog (GrouP h, SeqEntryPtr sep, CharPtr name, 
                                            Boolean force_string,
                                            TaglistCallback tlp_callback, 
                                            Pointer callback_data)
{
  SingleGBQualEditPtr dlg;
  GrouP           p;
  Int4            i;

  p = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (SingleGBQualEditPtr) MemNew (sizeof(SingleGBQualEditData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToSingleGBQualEditDialog;
  dlg->fromdialog = SingleGBQualEditDialogToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = GBQualEditDialogMessage;
  dlg->tlp_callback = tlp_callback;
  dlg->callback_data = callback_data;
  dlg->force_string = force_string;

  dlg->name = name;
  dlg->edit_list = NULL;
  for (i = 0; gbqual_edit_list[i].name != NULL && dlg->edit_list == NULL; i++) {
    if (StringICmp (gbqual_edit_list[i].name, name) == 0) {
      dlg->edit_list = gbqual_edit_list + i;
    }
  }

  dlg->copy_text = NULL;

  if (dlg->edit_list == NULL || dlg->edit_list->build_dlg == NULL || force_string) {
    dlg->txt = DialogText (p, "", 20, ChangeGBQualEditorText);
    SetObjectExtra (dlg->txt, dlg, NULL);
    dlg->editor = NULL;
    dlg->choice_grp = NULL;
  } else {
    dlg->choice_grp = HiddenGroup (p, 0, 2, ChangeSingleQualEdit);
    SetObjectExtra (dlg->choice_grp, dlg, NULL);
    dlg->use_editor = RadioButton (dlg->choice_grp, "");
    dlg->use_text = RadioButton (dlg->choice_grp, "");
    dlg->editor = (dlg->edit_list->build_dlg) (dlg->choice_grp, sep, name, tlp_callback, callback_data);
    dlg->txt = DialogText (dlg->choice_grp, "", 20, ChangeGBQualEditorText);
    SetObjectExtra (dlg->txt, dlg, NULL);
    SetValue (dlg->choice_grp, 1);
    if (dlg->edit_list->copy_txt != NULL) {
      dlg->copy_text = PushButton (dlg->choice_grp, "Copy Text", GBQualEditDialogCopyText);
      SetObjectExtra (dlg->copy_text, dlg, NULL);
    }
//    AlignObjects (ALIGN_LOWER, (HANDLE) dlg->use_text, (HANDLE) dlg->txt, NULL);
    AlignObjects (ALIGN_LOWER, (HANDLE) dlg->txt, (HANDLE) dlg->use_text, NULL);
    ChangeSingleQualEdit (dlg->choice_grp);
  }

  return (DialoG) p;
}

typedef struct newfieldpage {
  DIALOG_MESSAGE_BLOCK
  Int2               numfields;
  DialoG             PNTR editors;

  GBQualPtr          new_gbq;
  Uint1              subtype;
  Boolean            allowProductGBQual;
  Int2               last_viewed;
} NewFieldPage, PNTR NewFieldPagePtr;

static void CleanupNewFieldsPage (GraphiC g, VoidPtr data)

{
  NewFieldPagePtr  fpf;

  fpf = (NewFieldPagePtr) data;
  if (fpf != NULL) {
    MemFree (fpf->editors);
    fpf->new_gbq = GBQualFree (fpf->new_gbq);
  }
  MemFree (data);
}

static CharPtr GetDisplayQualName (CharPtr qual_name, Boolean is_legal)
{
  CharPtr display_name;

  if (is_legal) {
    display_name = StringSave (qual_name);
  } else {
    display_name = (CharPtr) MemNew (sizeof (Char) * (StringLen (qual_name) + 2));
    sprintf (display_name, "*%s", qual_name);
  }
  return display_name;
}

static Boolean QualNamesMatch (CharPtr qual_name1, CharPtr qual_name2)
{
  if (StringICmp (qual_name1, qual_name2) == 0) {
    return TRUE;
  } else if (qual_name1 != NULL 
             && *(qual_name1) == '*' 
             && StringICmp (qual_name1 + 1, qual_name2) == 0) {
    return TRUE;
  } else if (qual_name2 != NULL
             && *qual_name2 == '*'
             && StringICmp (qual_name1, qual_name2 + 1) == 0) {
    return TRUE;
  } else {
    return FALSE;
  }
}

static Boolean IsCombinableQual (Int2 qual)
{
  Boolean   combine_qual = FALSE;

  if (qual == GBQUAL_rpt_type 
      || qual == GBQUAL_rpt_unit 
      || qual == GBQUAL_rpt_unit_range
      || qual == GBQUAL_rpt_unit_seq 
      || qual == GBQUAL_replace 
      || qual == GBQUAL_compare
      || qual == GBQUAL_old_locus_tag
      || qual == GBQUAL_usedin) {
    combine_qual = TRUE;
  }
  return combine_qual;
}

static void AddTemporaryGBQual (GBQualPtr PNTR gbq_list, CharPtr qual_name, GBQualPtr gbq_feat, Boolean is_legal)
{
  Int2 qual;
  GBQualPtr gbq_last = NULL;
  GBQualPtr gbq_new = NULL;
  Boolean   found = FALSE;
  Boolean   combine_qual = FALSE;
  CharPtr   blank_val = "\"\"";

  if (gbq_list == NULL) return;

  gbq_last = *gbq_list;
  while (gbq_last != NULL && gbq_last->next != NULL) {
    if (QualNamesMatch (gbq_last->qual, qual_name)) {
      /* already added */
      return;
    }
    gbq_last = gbq_last->next;
  }
  if (gbq_last != NULL && QualNamesMatch (gbq_last->qual, qual_name)) {
    /* already added */
    return;
  }

  /* add value.  If none in feature, if true/false, add TRUE or FALSE, else use blank. */
  qual = GBQualNameValid (qual_name);

  combine_qual = IsCombinableQual (qual);

  while (gbq_feat != NULL) {
    if (StringICmp (qual_name, gbq_feat->qual) == 0) {
      if (StringHasNoText (gbq_feat->val)) {
        if (qual > -1 && ParFlat_GBQual_names [qual].gbclass == Class_none && !found) {
          /* if this is a true-false, only add one qualifier */
          gbq_new = GBQualNew ();
          gbq_new->qual = GetDisplayQualName (qual_name, is_legal);
          gbq_new->val = StringSave ("TRUE");
          if (gbq_last == NULL) {
            *gbq_list = gbq_new;
          } else {
            gbq_last->next = gbq_new;
          }
          gbq_last = gbq_new;
          found = TRUE;
        } else if (qual == GBQUAL_replace) {
          /* save blank values */
          gbq_new = NULL;
          if (found && combine_qual) {
            gbq_new = *gbq_list;
            while (gbq_new != NULL && !QualNamesMatch (gbq_feat->qual, gbq_new->qual)) {
              gbq_new = gbq_new->next;
            }
          }
          if (gbq_new == NULL) {
            /* make new qualifier */
            gbq_new = GBQualNew ();
            gbq_new->qual = GetDisplayQualName (qual_name, is_legal);
            gbq_new->val = StringSave (blank_val);
            /* add to list */
            if (gbq_last == NULL) {
              *gbq_list = gbq_new;
            } else {
              gbq_last->next = gbq_new;
            }
            gbq_last = gbq_new;
          } else {
            /* combine with previous value */
            gbq_new->val = CombineSplitGBQual (gbq_new->val, blank_val);
          }
          found = TRUE;
        } else {
          /* if not true-false or already have true-false, we'll just be adding a blank value later */    
          /* so do nothing here */
        }
      } else {
        gbq_new = NULL;
        if (found && combine_qual) {
          gbq_new = *gbq_list;
          while (gbq_new != NULL && !QualNamesMatch (gbq_feat->qual, gbq_new->qual)) {
            gbq_new = gbq_new->next;
          }
        }
        if (gbq_new == NULL) {
          /* make new qualifier */
          gbq_new = GBQualNew ();
          gbq_new->qual = GetDisplayQualName (qual_name, is_legal);
          gbq_new->val = StringSave (gbq_feat->val);
          /* add to list */
          if (gbq_last == NULL) {
            *gbq_list = gbq_new;
          } else {
            gbq_last->next = gbq_new;
          }
          gbq_last = gbq_new;
        } else {
          /* combine with previous value */
          gbq_new->val = CombineSplitGBQual (gbq_new->val, gbq_feat->val);
        }
        found = TRUE;
      }
    }
    gbq_feat = gbq_feat->next;
  }
   
  if (!found) {
    gbq_new = GBQualNew ();
    gbq_new->qual = GetDisplayQualName (qual_name, is_legal);
    if (qual > -1 && ParFlat_GBQual_names [qual].gbclass == Class_none) {
      gbq_new->val = StringSave ("FALSE");
    } else {
      gbq_new->val = StringSave ("");
    }
    if (gbq_last == NULL) {
      *gbq_list = gbq_new;
    } else {
      gbq_last->next = gbq_new;
    }
  }
}

static Boolean IsRarelyUsed (Int2 qual)
{
  if (qual == GBQUAL_allele
      || qual == GBQUAL_function
      || qual == GBQUAL_map
      || qual == GBQUAL_standard_name
      || qual == GBQUAL_old_locus_tag) {
    return TRUE;
  } else {
    return FALSE;
  }
}

static Pointer NewDialogToImportFields (DialoG d)
{
  NewFieldPagePtr fpf;
  GBQualPtr       gbq_list = NULL, gbq_last = NULL, gbq_new, gbq_it;
  Int2            qual, i;
  CharPtr         val, qual_name;

  fpf = (NewFieldPagePtr) GetObjectExtra (d);
  if (fpf == NULL) return NULL;

  for (gbq_it = fpf->new_gbq, i = 0; gbq_it != NULL; gbq_it = gbq_it->next, i++) {
    gbq_it->val = MemFree (gbq_it->val);
    gbq_it->val = DialogToPointer (fpf->editors[i]);
  }

  for (gbq_it = fpf->new_gbq; gbq_it != NULL; gbq_it = gbq_it->next) {
    if (StringHasNoText (gbq_it->val)) {
      continue;
    }
    qual_name = gbq_it->qual;
    if (qual_name != NULL && *qual_name == '*') {
      qual_name++;
    }
    qual = GBQualNameValid (qual_name);

    val = gbq_it->val;
    if (qual > -1 && ParFlat_GBQual_names [qual].gbclass == Class_none) {
      if (StringICmp (val, "TRUE") == 0) {
        /* don't put "true" in qual, just use empty string */
        val = "";
      } else if (StringICmp (val, "FALSE") == 0) {
        /* don't add FALSE qual */
        continue;
      }
      /* any other values, add as they are */
    } 

    gbq_new = GBQualNew();
    gbq_new->qual = StringSave (qual_name);
    gbq_new->val = StringSave (val);
    if (gbq_last == NULL) {
      gbq_list = gbq_new;
    } else {
      gbq_last->next = gbq_new;
    }
    gbq_last = gbq_new;
  }
  return (Pointer) gbq_list;
}

static void 
AddMandatoryAndOptionalQuals 
(CharPtr         name,
 NewFieldPagePtr fpf,
 GBQualPtr       gbq, 
 Boolean         use_rarely_used)
{
  Int2 index, i, qual;
  SematicFeatPtr  sefp;

  if (name == NULL) return;

  index = GBFeatKeyNameValid (&name, FALSE);
  if (index < 0) return;

  sefp = &(ParFlat_GBFeat [index]);
  /* add mandatory quals first */
  for (i = 0; i < sefp->mand_num; i++) {
    qual = sefp->mand_qual [i];
    if (qual > -1 
        && ShouldBeAGBQual (fpf->subtype, qual, fpf->allowProductGBQual)
        && ((use_rarely_used && IsRarelyUsed (qual))
            || (!use_rarely_used && ! IsRarelyUsed (qual)))) {
      AddTemporaryGBQual (&(fpf->new_gbq), ParFlat_GBQual_names [qual].name, gbq, TRUE);
    }
  }
  /* add optional quals next */
  for (i = 0; i < sefp->opt_num; i++) {
    qual = sefp->opt_qual [i];
    if (qual > -1 
        && ShouldBeAGBQual (fpf->subtype, qual, fpf->allowProductGBQual)
        && ((use_rarely_used && IsRarelyUsed (qual))
            || (!use_rarely_used && ! IsRarelyUsed (qual)))) {
      AddTemporaryGBQual (&(fpf->new_gbq), ParFlat_GBQual_names [qual].name, gbq, TRUE);
    }
  }
}

static GBQualPtr CopyGBQualList (GBQualPtr origgbq)

{
  GBQualPtr  gbq, gbq_new, head = NULL, last = NULL;

  for (gbq = origgbq; gbq != NULL; gbq = gbq->next) {
    gbq_new = GBQualNew ();
    gbq_new->qual = StringSave (gbq->qual);
    gbq_new->val = StringSave (gbq->val);
    if (head == NULL) {
      head = gbq_new;
    }
    if (last != NULL) {
      last->next = gbq_new;
    }
    last = gbq_new;
  }

  return head;
}


static void ChangeRptTypeForSatellite (Pointer data)
{
  NewFieldPagePtr fpf;
  GBQualPtr       gbq_it;
  Int4            i;
  Int4            rpt_type_num = -1;
  Boolean         tandem = FALSE, found_satellite = FALSE;
  CharPtr         val, new_val, src;
  CharPtr         fmt = "(tandem,%s)";

  fpf = (NewFieldPagePtr) data;

  if (fpf == NULL) {
    return;
  }

  /* find satellite qualifier, get type */
  for (gbq_it = fpf->new_gbq, i = 0; gbq_it != NULL; gbq_it = gbq_it->next, i++) {
    if (StringICmp (gbq_it->qual, "satellite") == 0) {
      val = DialogToPointer (fpf->editors[i]);
      if (StringCmp (val, "microsatellite") == 0 
          || StringCmp (val, "minisatellite") == 0
          || StringNICmp (val, "microsatellite:", 15) == 0
          || StringNICmp (val, "minisatellite:", 14) == 0) {
        tandem = TRUE;
      }
      val = MemFree (val);
      found_satellite = TRUE;
    } else if (StringICmp (gbq_it->qual, "rpt_type") == 0) {
      rpt_type_num = i;
    }
  }

  /* now set rpt_type */
  if (found_satellite && rpt_type_num > -1) {
    val = DialogToPointer (fpf->editors[rpt_type_num]);

    if (tandem) {
      if (StringISearch (val, "tandem") == NULL) {
        /* add tandem */
        if (StringHasNoText (val)) {
          PointerToDialog (fpf->editors[rpt_type_num], "tandem");
        } else {
          src = StringChr (val, '}');
          if (src != NULL) {
            *src = 0;
          }
          src = val;
          if (*src == '{') {
            src++;
          }
          new_val = (CharPtr) MemNew (sizeof (Char) * (StringLen (src) + StringLen (fmt)));                        
          sprintf (new_val, fmt, src);
          PointerToDialog (fpf->editors[rpt_type_num], new_val);
          new_val = MemFree (new_val);
        }
      }
    }

    /* set */

    val = MemFree (val);
  }
}


extern DialoG NewCreateImportFields (GrouP h, CharPtr name, SeqFeatPtr sfp, Boolean allowProductGBQual)
{
  NewFieldPagePtr fpf;
  GrouP           g, g1, g2 = NULL;
  GBQualPtr       gbq = NULL, gbq_it;
  Int2            j;
  Int2            max;
  Int2            num = 0, num_legal = 0;
  GrouP           p;
  Int2            qual;
  Int2            wid;
  PrompT          ill_q = NULL;
  size_t          len;
  CharPtr         ptr;

  p = HiddenGroup (h, -1, 0, NULL);
  fpf = (NewFieldPagePtr) MemNew (sizeof (NewFieldPage));
  if (fpf != NULL) {

    SetObjectExtra (p, fpf, CleanupNewFieldsPage);
    fpf->dialog = (DialoG) p;
    fpf->todialog = NULL;
    fpf->fromdialog = NewDialogToImportFields;
    fpf->testdialog = NULL;

    fpf->allowProductGBQual = allowProductGBQual;
    if (sfp == NULL) {
      fpf->subtype = FEATDEF_ANY;
    } else {
      fpf->subtype = sfp->idx.subtype;
      gbq = CopyGBQualList (sfp->qual); /* make copy so it is not corrupted */
    }

    fpf->last_viewed = -1;

    /* create list of temporary GBQuals */
    /* list mandatory/optional quals that are not rarely used first */
    AddMandatoryAndOptionalQuals (name, fpf, gbq, FALSE);

    /* add rarely used quals here */
    AddMandatoryAndOptionalQuals (name, fpf, gbq, TRUE);

    /* count legal values - all others to be listed as simple strings */
    for (gbq_it = fpf->new_gbq; gbq_it != NULL; gbq_it = gbq_it->next) {
      num_legal++;
    }

    /* add legal added qualifiers next */
    gbq_it = gbq;
    while (gbq_it != NULL) {
      qual = GBQualNameValid (gbq_it->qual);
      if (qual > -1 && ShouldBeAGBQual (fpf->subtype, qual, fpf->allowProductGBQual)) {
        AddTemporaryGBQual (&(fpf->new_gbq), ParFlat_GBQual_names [qual].name, gbq, FALSE);
      } 
      gbq_it = gbq_it->next;
    }

    /* add remaining qualifiers last */
    gbq_it = gbq;
    while (gbq_it != NULL) {  
      if (!ShouldSuppressGBQual(fpf->subtype, gbq_it->qual)) {
        AddTemporaryGBQual (&(fpf->new_gbq), gbq_it->qual, gbq, FALSE);
      }
      gbq_it = gbq_it->next;
    }

    /* replace {} combined qual bookends with () */
    for (gbq_it = fpf->new_gbq; gbq_it != NULL; gbq_it = gbq_it->next) {
      ptr = gbq_it->val;
      if (StringHasNoText (ptr)) continue;
      len = StringLen (ptr);
      if (len > 2 && ptr [0] == '{' && ptr [len - 1] == '}') {
        ptr [0] = '(';
        ptr [len - 1] = ')';
      }
    }

    /* calculate maximum name width */
    SelectFont (systemFont);
    max = 0;
    for (gbq_it = fpf->new_gbq; gbq_it != NULL; gbq_it = gbq_it->next) {
      num++;
      wid = StringWidth (gbq_it->qual) + 2;
      if (wid > max) {
        max = wid;
      }
    }
    SelectFont (systemFont);

    fpf->numfields = num;

    if (num > 0) {
      g1 = HiddenGroup (p, 2, 0, NULL);     
      g = g1;
      fpf->editors = MemNew (sizeof (DialoG) * (num + 1));
      for (gbq_it = fpf->new_gbq, j = 0; gbq_it != NULL; gbq_it = gbq_it->next) {
        if (j == num_legal) {
          ill_q = StaticPrompt (p, "*** Illegal Qualifiers ***", 0, dialogTextHeight, programFont, 'l');
          g2 = HiddenGroup (p, 2, 0, NULL);
          g = g2;
        }
        StaticPrompt (g, gbq_it->qual, 0, dialogTextHeight, programFont, 'l');
        fpf->editors[j] = CreateSingleGBQualEditDialog (g, NULL, gbq_it->qual,
                                                        j < num_legal ? FALSE : TRUE,
                                                        StringCmp (gbq_it->qual, "satellite") == 0 ? ChangeRptTypeForSatellite : NULL,
                                                        StringCmp (gbq_it->qual, "satellite") == 0 ? fpf : NULL);
        PointerToDialog (fpf->editors[j], gbq_it->val);
        j++;
      }   
      AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) ill_q, (HANDLE) g2, NULL);  
    } else {
      fpf->editors = NULL;
      StaticPrompt (p, "See Attributes page to set legal qualifiers for this feature.",
                    0, 0, programFont, 'c');
    }
  }
  GBQualFree (gbq); /* free copy */
  return (DialoG) p;
}


typedef struct specialcharacterdialog 
{
  TexT PNTR       text_list;
  ButtoN          accept_btn;
  Int4            num_chars;
  ValNodePtr      find_list;
} SpecialCharacterDialogData, PNTR SpecialCharacterDialogPtr;  


static void EnableSpecialCharacterAccept (TexT t)
{
  SpecialCharacterDialogPtr sd;
  Int4                      pos;
  CharPtr                   str;
  Boolean                   has_bad = FALSE;

  sd = (SpecialCharacterDialogPtr) GetObjectExtra (t);
  if (sd == NULL) return;

  for (pos = 0; pos < sd->num_chars && !has_bad; pos++)
  {
    str = SaveStringFromText (sd->text_list[pos]);
    SpecialCharFind (&str, NULL, &has_bad, NULL);
    str = MemFree (str);
  }
  if (has_bad) 
  {
    Disable (sd->accept_btn);
  }
  else
  {
    Enable (sd->accept_btn);
  }
}


static void SetWindowsSpecialCharacterDefaults (ButtoN b)
{
  SpecialCharacterDialogPtr sd;
  Int4                      pos;
  CharPtr                   str;
  ValNodePtr                vnp;

  sd = (SpecialCharacterDialogPtr) GetObjectExtra (b);
  if (sd == NULL) return;

  for (vnp = sd->find_list, pos = 0; vnp != NULL; vnp = vnp->next, pos++)
  {
    str = GetSpecialWinCharacterReplacement ((unsigned char) vnp->choice);
    SetTitle (sd->text_list[pos], str);
  }
}


static void SetMacSpecialCharacterDefaults (ButtoN b)
{
  SpecialCharacterDialogPtr sd;
  Int4                      pos;
  CharPtr                   str;
  ValNodePtr                vnp;

  sd = (SpecialCharacterDialogPtr) GetObjectExtra (b);
  if (sd == NULL) return;

  for (vnp = sd->find_list, pos = 0; vnp != NULL; vnp = vnp->next, pos++)
  {
    str = GetSpecialMacCharacterReplacement ((unsigned char) vnp->choice);
    SetTitle (sd->text_list[pos], str);
  }

}


extern Boolean FixSpecialCharactersForStringsInList (ValNodePtr find_list, CharPtr exp_text, Boolean force_fix)
{
  ValNodePtr      vnp, context_list, vnp_c;
  Int4            pos;
  CharPtr         repl;
  WindoW          w;
  GrouP           h, c, g, p1, g2;
  PrompT          p2;
  LisT            contexts;
  ButtoN          b;
  SpecialCharacterDialogData sd;
  ModalAcceptCancelData      acd;
  Char                  label[2];
  Int4                  num_contexts;
  Boolean               rval = FALSE;

  if (find_list == NULL) {
    return TRUE;
  } 

  ArrowCursor();
  Update();

  sd.find_list = find_list;
  sd.num_chars = ValNodeLen (find_list);
  
  acd.accepted = FALSE;
  acd.cancelled = FALSE;
  
  w = ModalWindow(-20, -13, -10, -10, NULL);
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  
  p1 = MultiLinePrompt (h, exp_text, 27 * stdCharWidth, programFont);
  p2 = StaticPrompt (h, "Choose replacement characters.", 0, 0, programFont, 'l');

  g = HiddenGroup (h, 3, 0, NULL);
  StaticPrompt (g, "Character", 0, 0, programFont, 'l');
  StaticPrompt (g, "Replacement", 0, 0, programFont, 'l');
  StaticPrompt (g, "Contexts", 0, 0, programFont, 'l');

  sd.text_list = MemNew (sizeof (TexT) * sd.num_chars);
  label[1] = 0;
  for (vnp = find_list, pos = 0; vnp != NULL; vnp = vnp->next, pos++)
  {
    label[0] = vnp->choice;
    StaticPrompt (g, label, 0, 0, programFont, 'l');
    repl = GetSpecialCharacterReplacement (vnp->choice);
    sd.text_list[pos] = DialogText (g, repl, 5, EnableSpecialCharacterAccept);
    SetObjectExtra (sd.text_list[pos], &sd, NULL);
    context_list = vnp->data.ptrvalue;
    num_contexts = ValNodeLen (context_list);
    if (num_contexts == 0)
    {
      StaticPrompt (g, "", 0, 0, programFont, 'l');
    }
    else if (num_contexts == 1)
    {
      StaticPrompt (g, *((CharPtr PNTR)context_list->data.ptrvalue), 0, 0, programFont, 'l');
    }
    else
    {
      contexts = SingleList (g, 16, MIN (num_contexts, 5), NULL);
      for (vnp_c = context_list; vnp_c != NULL; vnp_c = vnp_c->next)
      {
        ListItem (contexts, *((CharPtr PNTR)vnp_c->data.ptrvalue));
      }
      SetValue (contexts, 1);
    }
  }

  g2 = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (g2, 10, 10);
  b = PushButton (g2, "Suggest Windows Replacements", SetWindowsSpecialCharacterDefaults);
  SetObjectExtra (b, &sd, NULL);
  b = PushButton (g2, "Suggest Mac Replacements", SetMacSpecialCharacterDefaults);
  SetObjectExtra (b, &sd, NULL);

  c = HiddenGroup (h, 3, 0, NULL);
  SetGroupSpacing (c, 10, 10);
  sd.accept_btn = PushButton (c, "Replace Characters", ModalAcceptButton);
  SetObjectExtra (sd.accept_btn, &acd, NULL);
  if (!force_fix) {
    b = PushButton (c, "Cancel", ModalCancelButton);
    SetObjectExtra (b, &acd, NULL);
  }
  AlignObjects (ALIGN_CENTER, (HANDLE) p1, (HANDLE) p2, (HANDLE) g, (HANDLE) g2, (HANDLE) c, NULL);
  
  Show(w); 
  Select (w);
  while (!acd.accepted && ! acd.cancelled)
  {
    ProcessExternalEvent ();
    Update ();
  }
  ProcessAnEvent ();
  if (acd.accepted)
  {
    for (vnp = find_list, pos = 0; vnp != NULL; vnp = vnp->next, pos++)
    {
      repl = JustSaveStringFromText (sd.text_list[pos]);
      label[0] = vnp->choice;
      for (vnp_c = vnp->data.ptrvalue; vnp_c != NULL; vnp_c = vnp_c->next)
      {
        FindReplaceString (vnp_c->data.ptrvalue, label, repl, TRUE, FALSE);
      }
      repl = MemFree (repl);
    }

    rval = TRUE;
  }
  else if (force_fix)
  {
    for (vnp = find_list; vnp != NULL; vnp = vnp->next)
    {
      label[0] = vnp->choice;
      for (vnp_c = vnp->data.ptrvalue; vnp_c != NULL; vnp_c = vnp_c->next)
      {
        FindReplaceString (vnp_c->data.ptrvalue, label, "#", TRUE, FALSE);
      }
    }
    rval = TRUE;
  }

  Remove (w);
  return rval;

}


NLM_EXTERN Boolean FixSpecialCharacters (Uint2 entityID)
{
  ValNodePtr      find_list = NULL;
  Boolean         rval;

  StringActionInEntity (entityID, FALSE, UPDATE_NEVER, NULL, NULL, NULL, TRUE,
                        SpecialCharFindWithContext, NULL, &find_list);

  rval = FixSpecialCharactersForStringsInList (find_list,
              "The ASN.1 contains special characters.\nIf you save it without stripping them,\nyou will be unable to load the file in Sequin.",
              FALSE);

  find_list = FreeContextList (find_list);
  if (rval) 
  {
    ObjMgrSetDirtyFlag (entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, entityID, 0, 0);
  }
  return rval;
}


NLM_EXTERN Boolean FixSpecialCharactersForObject (Uint2 datatype, Pointer objdata, CharPtr msg, Boolean force_fix, BoolPtr changed)
{
  ValNodePtr      find_list = NULL;
  Boolean         rval;

  StringActionForObject (datatype, objdata, 0, FALSE, UPDATE_NEVER,
                        SpecialCharFindWithContext, NULL, &find_list);

  rval = FixSpecialCharactersForStringsInList (find_list,
              msg == NULL ?
                     "The ASN.1 contains special characters.\nIf you save it without stripping them,\nyou will be unable to load the file in Sequin."
                     : msg,
              force_fix);

  if (changed != NULL)
  {
    if (find_list == NULL)
    {
      *changed = FALSE;
    }
    else
    {
      *changed = rval;
    }
  } 

  find_list = FreeContextList (find_list);
  return rval;
}




typedef struct featurereplace {
  SeqFeatPtr sfp;
  Boolean    chosen;
  ValNodePtr replace_list;
} FeatureReplaceData, PNTR FeatureReplacePtr;


static FeatureReplacePtr FeatureReplaceNew (SeqFeatPtr sfp)
{
  FeatureReplacePtr f;

  f = (FeatureReplacePtr) MemNew (sizeof (FeatureReplaceData));
  f->sfp = AsnIoMemCopy (sfp, (AsnReadFunc) SeqFeatAsnRead, (AsnWriteFunc) SeqFeatAsnWrite);
  f->chosen = TRUE;
  f->replace_list = NULL;
  return f;
}


static FeatureReplacePtr FeatureReplaceCopy (FeatureReplacePtr orig)
{
  FeatureReplacePtr f = NULL;
  

  if (orig != NULL) {
    f = FeatureReplaceNew (orig->sfp);
    f->chosen = orig->chosen;
    f->replace_list = ValNodeCopyPtr (orig->replace_list);
  }
  return f;
}


static FeatureReplacePtr FeatureReplaceFree (FeatureReplacePtr f)
{
  if (f != NULL) {
    f->sfp = SeqFeatFree (f->sfp);
    f->replace_list = ValNodeFree (f->replace_list);
    f = MemFree (f);
  }
  return f;
}


NLM_EXTERN ValNodePtr FeatureReplaceListFree (ValNodePtr vnp)
{
  ValNodePtr vnp_next;

  while (vnp != NULL) {
    vnp_next = vnp->next;
    vnp->next = NULL;
    vnp->data.ptrvalue = FeatureReplaceFree (vnp->data.ptrvalue);
    vnp = ValNodeFree (vnp);
    vnp = vnp_next;
  }
  return vnp;
}


static ValNodePtr FeatureReplaceListCopy (ValNodePtr orig)
{
  ValNodePtr list = NULL;

  while (orig != NULL) {
    ValNodeAddPointer (&list, orig->choice, FeatureReplaceCopy (orig->data.ptrvalue));
    orig = orig->next;
  }
  return list;
}


NLM_EXTERN ValNodePtr FeatureReplaceListFromSeqAnnot (SeqAnnotPtr sap)
{
  SeqFeatPtr sfp;
  ValNodePtr list = NULL;

  if (sap == NULL || sap->type != 1) {
    return NULL;
  }

  sfp = sap->data;
  while (sfp != NULL) {
    ValNodeAddPointer (&list, 0, FeatureReplaceNew (sfp));
    sfp = sfp->next;
  }
  return list;
}


static Boolean DoFeaturesAutomatchForUpdate (SeqFeatPtr sfp1, SeqFeatPtr sfp2)
{
  GeneRefPtr grp1, grp2;
  Boolean    rval = FALSE;
  GBQualPtr  gbq;
  SeqIdPtr   sip;
  BioseqPtr  pbsp;

  if (sfp1 == NULL || sfp2 == NULL) {
    rval = FALSE;
  } else if (sfp1->data.choice != sfp2->data.choice) {
    rval = FALSE;
  } else if (sfp1->data.choice == SEQFEAT_GENE) {
    grp1 = sfp1->data.value.ptrvalue;
    grp2 = sfp2->data.value.ptrvalue;
    if (StringCmp (grp1->locus_tag, grp2->locus_tag) == 0) {
      rval = TRUE;
    }
  } else if (sfp1->data.choice == SEQFEAT_CDREGION) {
    /* match protein IDs */
    gbq = sfp1->qual;
    while (gbq != NULL && StringCmp (gbq->qual, "protein_id") != 0) {
      gbq = gbq->next;
    }
    if (gbq != NULL) {
      sip = MakeSeqID (gbq->val);
      /* look for protein ID on comparison feature */
      pbsp = BioseqFindFromSeqLoc (sfp2->product);
      if (pbsp != NULL && sip != NULL && SeqIdIn (sip, pbsp->id)) {
        rval = TRUE;
      }
      sip = SeqIdFree (sip);
    }
  }
  return rval;
}


static Boolean EntityIDAlreadyInList (Uint2 entityID, ValNodePtr entityIDList)
{
  while (entityIDList != NULL) {
    if ((Uint2)(entityIDList->data.intvalue) == entityID) {
      return TRUE;
    }
  }
  return FALSE;
}


NLM_EXTERN void ActOnFeatureReplaceList (ValNodePtr list)
{
  ValNodePtr vnp, vnp2;
  FeatureReplacePtr fr;
  BioseqPtr         bsp, product_bsp;
  SeqAnnotPtr       sap;
  SeqFeatPtr        sfp;
  ValNodePtr        entityIDList = NULL;
  SeqEntryPtr       sep;
  Int2              genCode;
  OMProcControl     ompc;

  for (vnp = list; vnp != NULL; vnp = vnp->next) {
    fr = (FeatureReplacePtr) vnp->data.ptrvalue;
    if (fr != NULL && fr->chosen && fr->sfp != NULL) {
      bsp = BioseqFindFromSeqLoc (fr->sfp->location);
      if (bsp != NULL) {
        sap = SeqAnnotNew ();
        sap->type = 1;        
        sap->data = AsnIoMemCopy (fr->sfp, (AsnReadFunc) SeqFeatAsnRead, (AsnWriteFunc) SeqFeatAsnWrite);
        MemSet ((Pointer) &ompc, 0, sizeof (OMProcControl));
        ompc.input_entityID = bsp->idx.entityID;
        ompc.input_itemID = GetItemIDGivenPointer (bsp->idx.entityID, OBJ_BIOSEQ, (Pointer) bsp);
        ompc.input_itemtype = OBJ_BIOSEQ;
        ompc.output_itemtype = OBJ_SEQANNOT;
        ompc.output_data = (Pointer) sap;
        if (! AttachDataForProc (&ompc, FALSE)) {
          Message (MSG_POSTERR, "Error attaching SeqAnnot");
        } else {
          for (vnp2 = fr->replace_list; vnp2 != NULL; vnp2 = vnp2->next) {
            sfp = vnp2->data.ptrvalue;
            if (sfp != NULL) {
              sfp->idx.deleteme = TRUE;
              if (sfp->product != NULL) {
                product_bsp = BioseqFindFromSeqLoc (sfp->product);
                if (product_bsp != NULL) {
                  product_bsp->idx.deleteme = TRUE;
                }
              }
            }
          }
          if (!EntityIDAlreadyInList (bsp->idx.entityID, entityIDList)) {
            ValNodeAddInt (&entityIDList, 0, bsp->idx.entityID);
          }
          sep = GetBestTopParentForData (bsp->idx.entityID, bsp);
          genCode = SeqEntryToGeneticCode (sep, NULL, NULL, 0);
          SetEmptyGeneticCodes (sap, genCode);
          PromoteXrefs (sap->data, bsp, bsp->idx.entityID);
        }
      }
    }
  }
  for (vnp = entityIDList; vnp != NULL; vnp = vnp->next) {
    DeleteMarkedObjects (vnp->data.intvalue, 0, NULL);
  }
}


typedef struct featurereplacelistdlg {
  DIALOG_MESSAGE_BLOCK
  DoC doc;
  ValNodePtr list;
  Int2       selected;
  Int2       clicked;
  Boolean    dblClick;

  Nlm_ParData ParFmt;
  Nlm_ColData NewFeatColFmt[2];
  Nlm_ColData OldFeatColFmt[2];

  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_userdata;
} FeatureReplaceListDlgData, PNTR FeatureReplaceListDlgPtr;


static void InitColAndParForFeatureReplaceList (DialoG d)
{
  FeatureReplaceListDlgPtr dlg;
  RecT r;
  Int4 doc_width;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return;
  }

  dlg->ParFmt.openSpace = FALSE;
  dlg->ParFmt.keepWithNext = FALSE;
  dlg->ParFmt.keepTogether = FALSE;
  dlg->ParFmt.newPage = FALSE;
  dlg->ParFmt.tabStops = FALSE;
  dlg->ParFmt.minLines = 0;
  dlg->ParFmt.minHeight = 0;

  ObjectRect (dlg->doc, &r);
  InsetRect (&r, 4, 4);
  doc_width = r.right - r.left;

  dlg->NewFeatColFmt[0].pixWidth = 16;
  dlg->NewFeatColFmt[0].pixInset = 0;
  dlg->NewFeatColFmt[0].charWidth = 0;
  dlg->NewFeatColFmt[0].charInset = 0;
  dlg->NewFeatColFmt[0].font = programFont;
  dlg->NewFeatColFmt[0].just = 'l';
  dlg->NewFeatColFmt[0].wrap = 0;
  dlg->NewFeatColFmt[0].bar = 0;
  dlg->NewFeatColFmt[0].underline = 0;
  dlg->NewFeatColFmt[0].left = 0;
  dlg->NewFeatColFmt[0].last = 0;

  dlg->NewFeatColFmt[1].pixWidth = doc_width - 16;
  dlg->NewFeatColFmt[1].pixInset = 0;
  dlg->NewFeatColFmt[1].charWidth = 0;
  dlg->NewFeatColFmt[1].charInset = 0;
  dlg->NewFeatColFmt[1].font = programFont;
  dlg->NewFeatColFmt[1].just = 'l';
  dlg->NewFeatColFmt[1].wrap = 1;
  dlg->NewFeatColFmt[1].bar = 0;
  dlg->NewFeatColFmt[1].underline = 0;
  dlg->NewFeatColFmt[1].left = 0;
  dlg->NewFeatColFmt[1].last = 1;

  
  dlg->OldFeatColFmt[0].pixWidth = 32;
  dlg->OldFeatColFmt[0].pixInset = 0;
  dlg->OldFeatColFmt[0].charWidth = 0;
  dlg->OldFeatColFmt[0].charInset = 0;
  dlg->OldFeatColFmt[0].font = programFont;
  dlg->OldFeatColFmt[0].just = 'l';
  dlg->OldFeatColFmt[0].wrap = 0;
  dlg->OldFeatColFmt[0].bar = 0;
  dlg->OldFeatColFmt[0].underline = 0;
  dlg->OldFeatColFmt[0].left = 0;
  dlg->OldFeatColFmt[0].last = 0;

  dlg->OldFeatColFmt[1].pixWidth = doc_width - 32;
  dlg->OldFeatColFmt[1].pixInset = 0;
  dlg->OldFeatColFmt[1].charWidth = 0;
  dlg->OldFeatColFmt[1].charInset = 0;
  dlg->OldFeatColFmt[1].font = programFont;
  dlg->OldFeatColFmt[1].just = 'l';
  dlg->OldFeatColFmt[1].wrap = 1;
  dlg->OldFeatColFmt[1].bar = 0;
  dlg->OldFeatColFmt[1].underline = 0;
  dlg->OldFeatColFmt[1].left = 0;
  dlg->OldFeatColFmt[1].last = 1;
}


/* calculates position of feature replace item and feature within replace item */
static Int2 GetListPosFromItem (ValNodePtr list, Int2 item, Int2Ptr feature_pos)
{
  Int2 num = 1, pos = 1, nextval;
  FeatureReplacePtr fr;
  ValNodePtr vnp;

  if (feature_pos != NULL) {
    *feature_pos = 0;
  }
  if (list == NULL || item < 1) {
    return -1;
  }

  vnp = list;

  while (num < item && vnp != NULL) {
    fr = (FeatureReplacePtr) vnp->data.ptrvalue;
    nextval = num + ValNodeLen (fr->replace_list) + 1;
    if (nextval < item ) {
      pos++;
      num = nextval;
    } else if (nextval == item) {
      pos++;
      if (feature_pos != NULL) {
        *feature_pos = 0;
      }
      return pos;
    } else {
      if (feature_pos != NULL) {
        *feature_pos = nextval - item;
      }
      return pos;
    }
    vnp = vnp->next;
  }
  return pos; 
}


static void DrawFeatureReplaceList (DoC d, RectPtr r, Int2 item, Int2 firstLine)

{
  FeatureReplaceListDlgPtr dlg;
  RecT                     rct;
  FeatureReplacePtr        fr;
  ValNodePtr               vnp;
  Int2                     pos, num, feature_pos = 0;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg != NULL && r != NULL && item > 0 && firstLine == 0) {
    rct = *r;
  
    vnp = dlg->list;
    pos = GetListPosFromItem (dlg->list, item, &feature_pos);
    if (pos < 1) {
      /* do nothing */
    } else if (feature_pos > 0) {
      /* do nothing */
    } else {
      /* draw box for selecting feature to be imported */
      num = 1;
      while (num < pos) {
        vnp = vnp->next;
        num++;
      }
      if (vnp != NULL && vnp->data.ptrvalue != NULL) {
        fr = (FeatureReplacePtr) vnp->data.ptrvalue;

        /* draw selection */
        if (item == dlg->selected) {
          rct.right = rct.left + 4;
          PaintRect (&rct);
        }

        /* draw chosen checkboxes */
        rct.left += 5;
        rct.right = rct.left + 10;
        rct.bottom = rct.top + (rct.right - rct.left);
        FrameRect (&rct);
      
        if (fr->chosen) {
          MoveTo (rct.left, rct.top);
          LineTo (rct.right - 1, rct.bottom - 1);
          MoveTo (rct.left, rct.bottom - 1);
          LineTo (rct.right - 1, rct.top);
        }
      }
    }
  }
}


static Boolean HighlightFeatureReplaceTarget (DoC doc, Int2 item, Int2 row, Int2 col)
{
  FeatureReplaceListDlgPtr dlg;
  
  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (doc);
  if (dlg == NULL) return FALSE;
    
  if (dlg->selected == item) 
  {
    return TRUE;
  } 
  else 
  {
    return FALSE;
  }
}

static CharPtr GetNewItemDescription (SeqFeatPtr sfp)
{
  CharPtr location, label, row_text;
  Char buf[129];
  
  if (sfp == NULL) {
    return StringSave ("Misc Features to Delete");
  }
  location = SeqLocPrintUseBestID (sfp->location);
  label = (CharPtr) FeatDefTypeLabel(sfp);

  FeatDefLabel (sfp, buf, sizeof (buf) - 1, OM_LABEL_CONTENT);

  row_text = (CharPtr) MemNew (sizeof (Char) * 
                              (StringLen (label) 
                              + StringLen (buf) 
                              + StringLen (location) 
                              + 6));
  sprintf (row_text, "%s:%s:%s\n", label, buf, location);
  location = MemFree (location);
  return row_text;
}


static void AddFeatureReplace (FeatureReplaceListDlgPtr dlg, FeatureReplacePtr fr)
{
  CharPtr            desc, item_text, desc_fmt = "\t%s", repl_fmt = "\tReplaces %s";
  ValNodePtr         vnp;

  if (dlg == NULL || fr == NULL)
  {
    return;
  }
  desc = GetNewItemDescription (fr->sfp);
  item_text = (CharPtr) MemNew (sizeof (Char) * (StringLen (desc_fmt) + StringLen (desc)));
  sprintf (item_text, desc_fmt, desc);
  desc = MemFree (desc);
  AppendText (dlg->doc, item_text, &dlg->ParFmt, dlg->NewFeatColFmt, programFont);
  MemFree (item_text);
  for (vnp = fr->replace_list; vnp != NULL; vnp = vnp->next) {
    desc = GetNewItemDescription (vnp->data.ptrvalue);
    item_text = (CharPtr) MemNew (sizeof (Char) * (StringLen (repl_fmt) + StringLen (desc)));
    sprintf (item_text, repl_fmt, desc);
    desc = MemFree (desc);
    AppendText (dlg->doc, item_text, &dlg->ParFmt, dlg->OldFeatColFmt, programFont);
    MemFree (item_text);
  }
}


static void ClickFeatureReplaceList (DoC d, PoinT pt)

{
  Int2             item, numItems, pos, feature_pos;
  Int2             row;
  Int2             col;
  FeatureReplaceListDlgPtr dlg;
  Int4             offset;
  Int2             first_shown = 0;
  RecT             r;
  BaR              vbar;
  Int4             scroll_pos;

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    MapDocPoint (d, pt, &item, &row, &col, NULL);
    if (item > 0 && row > 0 && dlg->clicked == item) {
      dlg->dblClick = dblClick;
    } else {
      dlg->dblClick = FALSE;
    }
    dlg->clicked = 0;
    if (item > 0 && row > 0) {
      dlg->clicked = item;
    }
    if (item > 0 && row > 0 && !dblClick)
    {
      vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
      scroll_pos = GetBarValue (vbar);

      pos = GetListPosFromItem (dlg->list, item, &feature_pos);
      if (pos >= 1 && col == 2 && feature_pos == 0) {
        dlg->selected = item;
      }
      GetDocParams (d, &numItems, NULL);
      UpdateDocument (d, 0, numItems);
      GetItemParams4 (dlg->doc, first_shown, &offset, NULL, NULL, NULL, NULL);
      SetScrlParams4 (dlg->doc, offset);
      ObjectRect (dlg->doc, &r);
      InsetRect (&r, -1, -1);
      InvalRect (&r);    
      SetBarValue (vbar, scroll_pos);
      if (dlg->change_notify != NULL) {
        (dlg->change_notify) (dlg->change_userdata);
      }
    }
  }
}


static void PopulateFeatureReplaceList (FeatureReplaceListDlgPtr dlg, ValNodePtr list)
{
  Int2               numItems;
  
  if (dlg == NULL || dlg->doc == NULL) 
  {
    return;
  }
  
  Reset (dlg->doc);
  
  
  while (list != NULL)
  {
    AddFeatureReplace (dlg, list->data.ptrvalue);
    list = list->next;
  }
  GetDocParams (dlg->doc, &numItems, NULL);
  UpdateDocument (dlg->doc, 0, numItems);

}


static void FeatureReplaceListToDialog (DialoG d, Pointer data)
{
  FeatureReplaceListDlgPtr dlg;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return;
  }
  dlg->list = FeatureReplaceListFree (dlg->list);
  dlg->list = FeatureReplaceListCopy((ValNodePtr) data);
  PopulateFeatureReplaceList (dlg, dlg->list);
}


static Pointer FeatureReplaceListFromDialog (DialoG d)
{
  FeatureReplaceListDlgPtr dlg;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return NULL;
  }
  return FeatureReplaceListCopy(dlg->list);
}


static void CleanupFeatureReplaceListDialog (GraphiC g, VoidPtr data)

{
  FeatureReplaceListDlgPtr dlg;

  dlg = (FeatureReplaceListDlgPtr) data;
  if (dlg != NULL) {
    dlg->list = FeatureReplaceListFree (dlg->list);
  }
  StdCleanupExtraProc (g, data);
}


NLM_EXTERN DialoG 
FeatureReplaceListDialog 
(GrouP h, 
 Int4 width,
 Nlm_ChangeNotifyProc change_notify,
 Pointer change_userdata)
{
  FeatureReplaceListDlgPtr dlg;
  GrouP                    p;

  dlg = (FeatureReplaceListDlgPtr) MemNew (sizeof (FeatureReplaceListDlgData));

  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, CleanupFeatureReplaceListDialog);

  dlg->dialog = (DialoG) p;
  dlg->fromdialog = FeatureReplaceListFromDialog;
  dlg->todialog = FeatureReplaceListToDialog;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->doc = DocumentPanel (p, width, stdLineHeight * 20);
  SetObjectExtra (dlg->doc, dlg, NULL);
  SetDocAutoAdjust (dlg->doc, FALSE);
  SetDocProcs (dlg->doc, ClickFeatureReplaceList, NULL, NULL, NULL);
  SetDocShade (dlg->doc, DrawFeatureReplaceList, NULL, HighlightFeatureReplaceTarget, NULL);

  InitColAndParForFeatureReplaceList (dlg->dialog);

  return (DialoG) p;
}


NLM_EXTERN Boolean AddFeaturesToReplaceList (DialoG d, ValNodePtr feature_list)
{
  FeatureReplaceListDlgPtr dlg;
  Int2                     num, pos;
  ValNodePtr               vnp = NULL;
  FeatureReplacePtr        fr;
  Boolean                  rval = FALSE;
  BaR                      vbar;
  Int4                     scroll_pos;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || feature_list == NULL) {
    return FALSE;
  }

  vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
  scroll_pos = GetBarValue (vbar);

  if (dlg->selected >= 1) {
    pos = GetListPosFromItem (dlg->list, dlg->selected, NULL);
    num = 1;
    vnp = dlg->list;
    while (num < pos && vnp != NULL) {
      num++;
      vnp = vnp->next;
    }
  }
  if (vnp != NULL) {
    fr = (FeatureReplacePtr) vnp->data.ptrvalue;
    if (fr != NULL) {
      ValNodeLink (&fr->replace_list, feature_list);
      PopulateFeatureReplaceList (dlg, dlg->list);
      rval = TRUE;
    }
  }
  if (!rval) {
    /* find misc item */
    vnp = dlg->list;
    while (vnp != NULL && ((fr = (FeatureReplacePtr) vnp->data.ptrvalue) == NULL
                           || fr->sfp != NULL)) {
      vnp = vnp->next;
    }
    if (vnp == NULL) {
      fr = FeatureReplaceNew (NULL);
      ValNodeAddPointer (&dlg->list, 0, fr);
    }
    ValNodeLink (&(fr->replace_list), feature_list);
    PopulateFeatureReplaceList (dlg, dlg->list);
    rval = TRUE;
  }

  SetBarValue (vbar, scroll_pos);

  return rval;
}


NLM_EXTERN SeqFeatPtr GetSelectedNewFeature (DialoG d)
{
  FeatureReplaceListDlgPtr dlg;
  FeatureReplacePtr fr;
  SeqFeatPtr sfp = NULL;
  ValNodePtr vnp;
  Int2       num, pos;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return NULL;
  }

  if (dlg->selected >= 1) {
    pos = GetListPosFromItem (dlg->list, dlg->selected, NULL);
    num = 1;
    vnp = dlg->list;
    while (num < pos && vnp != NULL) {
      num++;
      vnp = vnp->next;
    }
    if (vnp != NULL) {
      fr = (FeatureReplacePtr) vnp->data.ptrvalue;
      if (fr != NULL) {
        sfp = fr->sfp;
      }
    }
  }
  return sfp;
}


NLM_EXTERN ValNodePtr RemoveFeaturesFromReplaceList (DialoG d)
{
  FeatureReplaceListDlgPtr dlg;
  Int2                     num;
  ValNodePtr               vnp, list = NULL, prev = NULL;
  FeatureReplacePtr        fr;
  BaR                      vbar;
  Int4                     scroll_pos;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->list == NULL || dlg->selected < 1) {
    return NULL;
  }

  num = 1;
  vnp = dlg->list;
  while (num < dlg->selected && vnp != NULL) {
    num++;
    prev = vnp;
    vnp = vnp->next;
  }
  if (vnp != NULL) {
    fr = (FeatureReplacePtr) vnp->data.ptrvalue;
    if (fr != NULL) {
      list = fr->replace_list;
      fr->replace_list = NULL;
      /* if we have removed features from a placeholder empty category, remove the category */
      if (fr->sfp == NULL) {
        if (prev == NULL) {
          dlg->list->next = vnp->next;
        } else {
          prev->next = vnp->next;
        }
        vnp->next = NULL;
        vnp = FeatureReplaceListFree (vnp);
      }
      vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
      scroll_pos = GetBarValue (vbar);

      PopulateFeatureReplaceList (dlg, dlg->list);
      SetBarValue (vbar, scroll_pos);
    }
  }
  return list;
}


NLM_EXTERN Boolean AutomatchFeatures (DialoG d, ValNodePtr PNTR existing_features)
{
  FeatureReplaceListDlgPtr dlg;
  ValNodePtr               vnp, vnp_f, vnp_next, prev = NULL;
  FeatureReplacePtr        fr;
  Boolean                  found = FALSE;
  SeqIntPtr                s1, s2, s3;

  dlg = (FeatureReplaceListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->list == NULL || existing_features == NULL || *existing_features == NULL) {
    return FALSE;
  }

  for (vnp = dlg->list; vnp != NULL; vnp = vnp->next) {
    fr = (FeatureReplacePtr) vnp->data.ptrvalue;
    prev = NULL;
    s1 = (SeqIntPtr)(fr->sfp->location->data.ptrvalue);
    if (s1->from == 86391) {
      s2 = NULL;
    }
    for (vnp_f = *existing_features; vnp_f != NULL; vnp_f = vnp_next) {
      vnp_next = vnp_f->next;
      s2 = (SeqIntPtr)((SeqFeatPtr)vnp_f->data.ptrvalue)->location->data.ptrvalue;
      if (s2->from == 86391) {
        s3 = NULL;
      }
      if (DoFeaturesAutomatchForUpdate (fr->sfp, vnp_f->data.ptrvalue)) {
        if (prev == NULL) {
          *existing_features = vnp_f->next;
        } else {
          prev->next = vnp_f->next;
        }
        vnp_f->next = NULL;
        ValNodeLink (&(fr->replace_list), vnp_f);
        found = TRUE;
      } else {
        prev = vnp_f;
      }
    }
  }

  PopulateFeatureReplaceList (dlg, dlg->list);

  return found;
}



typedef struct featureselectlistdlg {
  DIALOG_MESSAGE_BLOCK
  DoC doc;
  ValNodePtr list;
  Int2       selected;
  Int2       clicked;
  Boolean    dblClick;

  Nlm_ParData ParFmt;
  Nlm_ColData ColFmt[2];
} FeatureSelectListDlgData, PNTR FeatureSelectListDlgPtr;


static void ClickFeatureSelectList (DoC d, PoinT pt)

{
  Int2             item, numItems;
  Int2             first_shown = 0;
  Int2             row;
  Int2             col;
  Int4             offset;
  FeatureSelectListDlgPtr dlg;
  RecT             r;
  BaR                     vbar;
  Int4                    scroll_pos;
  

  dlg = GetObjectExtra (d);
  if (dlg != NULL) {
    MapDocPoint (d, pt, &item, &row, &col, NULL);
    if (item > 0 && row > 0 && dlg->clicked == item) {
      dlg->dblClick = dblClick;
    } else {
      dlg->dblClick = FALSE;
    }
    dlg->clicked = 0;
    if (item > 0 && row > 0) {
      dlg->clicked = item;
      dlg->selected = item;
    }
    vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
    scroll_pos = GetBarValue (vbar);

    GetDocParams (d, &numItems, NULL);
    UpdateDocument (d, 0, numItems);
    GetItemParams4 (dlg->doc, first_shown, &offset, NULL, NULL, NULL, NULL);
    SetScrlParams4 (dlg->doc, offset);
    ObjectRect (dlg->doc, &r);
    InsetRect (&r, -1, -1);
    InvalRect (&r);
    SetBarValue (vbar, scroll_pos);
  }
}


static Boolean HighlightFeatureSelectTarget (DoC doc, Int2 item, Int2 row, Int2 col)
{
  FeatureSelectListDlgPtr dlg;
  
  dlg = (FeatureSelectListDlgPtr) GetObjectExtra (doc);
  if (dlg == NULL) return FALSE;
    
  if (dlg->selected == item) 
  {
    return TRUE;
  } 
  else 
  {
    return FALSE;
  }
}



static void PopulateFeatureSelectList (FeatureSelectListDlgPtr dlg, ValNodePtr list)
{
  ValNodePtr              vnp;
  CharPtr                 desc;
  Int2                    numItems;
  
  Reset (dlg->doc);
  for (vnp = list; vnp != NULL; vnp = vnp->next) {
    desc = GetNewItemDescription (vnp->data.ptrvalue);
    AppendText (dlg->doc, desc, &dlg->ParFmt, dlg->ColFmt, programFont);
    desc = MemFree (desc);
  }

  GetDocParams (dlg->doc, &numItems, NULL);
  UpdateDocument (dlg->doc, 0, numItems);   
}


static void FeatureSelectListToDialog (DialoG d, Pointer data)
{
  FeatureSelectListDlgPtr dlg;

  dlg = (FeatureSelectListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return;
  }

  dlg->list = (ValNodePtr) data;
  PopulateFeatureSelectList (dlg, dlg->list);
}


static Pointer FeatureSelectListFromDialog (DialoG d)
{
  FeatureSelectListDlgPtr dlg;

  dlg = (FeatureSelectListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return NULL;
  }

  return (Pointer) dlg->list;
}


NLM_EXTERN DialoG FeatureSelectListDialog (GrouP h, Int4 width)
{
  FeatureSelectListDlgPtr dlg;
  GrouP                   p;
  RecT                    r;
  Int4                    doc_width;

  dlg = (FeatureSelectListDlgPtr) MemNew (sizeof (FeatureSelectListDlgData));

  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);

  dlg->dialog = (DialoG) p;
  dlg->todialog = FeatureSelectListToDialog;
  dlg->fromdialog = FeatureSelectListFromDialog;

  dlg->doc = DocumentPanel (p, width, stdLineHeight * 20);
  SetObjectExtra (dlg->doc, dlg, NULL);
  SetDocAutoAdjust (dlg->doc, FALSE);
  SetDocShade (dlg->doc, NULL, NULL, HighlightFeatureSelectTarget, NULL);
  SetDocProcs (dlg->doc, ClickFeatureSelectList, NULL, NULL, NULL);

  dlg->ParFmt.openSpace = FALSE;
  dlg->ParFmt.keepWithNext = FALSE;
  dlg->ParFmt.keepTogether = FALSE;
  dlg->ParFmt.newPage = FALSE;
  dlg->ParFmt.tabStops = FALSE;
  dlg->ParFmt.minLines = 0;
  dlg->ParFmt.minHeight = 0;

  ObjectRect (dlg->doc, &r);
  InsetRect (&r, 4, 4);
  doc_width = r.right - r.left;

  dlg->ColFmt[0].pixWidth = doc_width;
  dlg->ColFmt[0].pixInset = 0;
  dlg->ColFmt[0].charWidth = 0;
  dlg->ColFmt[0].charInset = 0;
  dlg->ColFmt[0].font = programFont;
  dlg->ColFmt[0].just = 'l';
  dlg->ColFmt[0].wrap = 0;
  dlg->ColFmt[0].bar = 0;
  dlg->ColFmt[0].underline = 0;
  dlg->ColFmt[0].left = 0;
  dlg->ColFmt[0].last = 1;


  return (DialoG) p;
}


NLM_EXTERN ValNodePtr RemoveSelectedFeaturesFromList (DialoG d)
{
  FeatureSelectListDlgPtr dlg;
  ValNodePtr              vnp, prev = NULL;
  Int2                    num;
  BaR                     vbar;
  Int4                    scroll_pos;

  dlg = (FeatureSelectListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->selected == 0) {
    return NULL;
  }

  vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
  scroll_pos = GetBarValue (vbar);

  num = 1;
  vnp = dlg->list;
  while (num < dlg->selected && vnp != NULL) {
    prev = vnp;
    vnp = vnp->next;
    num++;
  }

  if (vnp != NULL) {
    if (prev == NULL) {
      dlg->list = vnp->next;
    } else {
      prev->next = vnp->next;
    }
    vnp->next = NULL;
  }
  PopulateFeatureSelectList (dlg, dlg->list);
  SetBarValue (vbar, scroll_pos);
  return vnp;
}


NLM_EXTERN void ScrollToMatchingFeatures (DialoG d, SeqFeatPtr sfp)
{
  FeatureSelectListDlgPtr dlg;
  ValNodePtr              vnp;
  BaR                     vbar;
  Int4                    scroll_pos;
  Int4                    start, stop, startitem;
  Int4                    range_start = -1, pos;
  RecT                    r;

  dlg = (FeatureSelectListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || sfp == NULL) {
    return;
  }

  dlg->selected = 0;
  start = SeqLocStart (sfp->location);
  stop = SeqLocStop (sfp->location);

  for (vnp = dlg->list, pos = 0; vnp != NULL; vnp = vnp->next, pos++) {
    startitem = SeqLocStart (((SeqFeatPtr)vnp->data.ptrvalue)->location);
    if (startitem >= start && range_start == -1) {
      range_start = pos;
    }
    if (DoFeaturesAutomatchForUpdate (sfp, vnp->data.ptrvalue)) {
      range_start = pos;
      dlg->selected = pos + 1;
      break;
    }
    if (startitem > stop) {
      break;
    }
  }

  /* scroll to position */
  if (range_start > -1) {
    vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
    scroll_pos = GetBarMax (vbar);
    if (scroll_pos > range_start) {
      scroll_pos = range_start;
    }
    SetBarValue (vbar, scroll_pos);
  }
  ObjectRect (dlg->doc, &r);
  InsetRect (&r, -1, -1);
  InvalRect (&r);
  Update ();

}


typedef struct featsort {
  SeqFeatPtr sfp;
  Int4       start;
  Int4       stop;
} FeatSortData, PNTR FeatSortPtr;


static FeatSortPtr FeatSortNew (SeqFeatPtr sfp)
{
  FeatSortPtr f;

  if (sfp == NULL) {
    return NULL;
  }

  f = (FeatSortPtr) MemNew (sizeof (FeatSortData));
  f->sfp = sfp;
  f->start = SeqLocStart (sfp->location);
  f->stop = SeqLocStop (sfp->location);
  return f;
}


static FeatSortPtr FeatSortFree (FeatSortPtr f)
{
  if (f != NULL) {
    f = MemFree (f);
  }
  return f;
}


static ValNodePtr FeatSortListFree (ValNodePtr vnp)
{
  ValNodePtr vnp_next;

  while (vnp != NULL) {
    vnp_next = vnp->next;
    vnp->next = NULL;
    vnp->data.ptrvalue = FeatSortFree (vnp->data.ptrvalue);
    vnp = ValNodeFree (vnp);
    vnp = vnp_next;
  }
  return vnp;
}


static ValNodePtr FeatSortListFromSeqAnnot (SeqAnnotPtr sap)
{
  ValNodePtr list = NULL, prev = NULL, vnp;
  SeqFeatPtr sfp;
  FeatSortPtr f;

  if (sap == NULL || sap->type != 1) {
    return NULL;
  }

  for (sfp = sap->data; sfp != NULL; sfp = sfp->next) {
    f = FeatSortNew (sfp);
    vnp = ValNodeNew (NULL);
    vnp->data.ptrvalue = f;
    if (prev == NULL) {
      list = vnp;
    } else {
      prev->next = vnp;
    }
    prev = vnp;
  }
  return list;
}


static SeqFeatPtr SeqFeatListFromFeatSortList (ValNodePtr list)
{
  SeqFeatPtr first = NULL, last = NULL;
  FeatSortPtr f;

  while (list != NULL) {
    f = (FeatSortPtr) list->data.ptrvalue;
    if (f != NULL) {
      if (first == NULL) {
        first = f->sfp;
      } else {
        last->next = f->sfp;
      }
      last = f->sfp;
      last->next = NULL;
    }
    list = list->next;
  }
  return first;
}


static ValNodePtr FeatSortListFromFeatValNodeList (ValNodePtr orig)
{
  ValNodePtr list = NULL, prev = NULL, vnp, vnp_orig;
  FeatSortPtr f;
  
  for (vnp_orig = orig; vnp_orig != NULL; vnp_orig = vnp_orig->next) {
    f = FeatSortNew ((SeqFeatPtr) vnp_orig->data.ptrvalue);
    vnp = ValNodeNew (NULL);
    vnp->data.ptrvalue = f;
    if (prev == NULL) {
      list = vnp;
    } else {
      prev->next = vnp;
    }
    prev = vnp;
  }
  return list;
}


static ValNodePtr FeatValNodeListFromFeatSortList (ValNodePtr orig)
{
  ValNodePtr list = NULL, prev = NULL, vnp, vnp_orig;
  FeatSortPtr f;
  
  for (vnp_orig = orig; vnp_orig != NULL; vnp_orig = vnp_orig->next) {
    f = (FeatSortPtr) vnp_orig->data.ptrvalue;
    vnp = ValNodeNew (NULL);
    vnp->data.ptrvalue = f->sfp;
    if (prev == NULL) {
      list = vnp;
    } else {
      prev->next = vnp;
    }
    prev = vnp;
  }
  return list;
}

static int LIBCALLBACK SortVnpByFeatSort (VoidPtr ptr1, VoidPtr ptr2)

{
  ValNodePtr  vnp1;
  ValNodePtr  vnp2;
  FeatSortPtr f1, f2;

  if (ptr1 != NULL && ptr2 != NULL) {
    vnp1 = *((ValNodePtr PNTR) ptr1);
    vnp2 = *((ValNodePtr PNTR) ptr2);
    if (vnp1 != NULL && vnp2 != NULL) {
      f1 = (FeatSortPtr) vnp1->data.ptrvalue;
      f2 = (FeatSortPtr) vnp2->data.ptrvalue;
      if (f1 != NULL && f2 != NULL) {
        if (f1->start < f2->start) {
          return -1;
        } else if (f1->start > f2->start) {
          return 1;
        } else if (f1->stop > f2->stop) {
          return -1;
        } else if (f1->stop < f2->stop) {
          return 1;
        } else if (f1->sfp->data.choice < f2->sfp->data.choice) {
          return -1;
        } else if (f1->sfp->data.choice > f2->sfp->data.choice) {
          return 1;
        } else {
          return 0;
        }
      }
    }
  }
  return 0;
}



NLM_EXTERN void SortSeqFeatInAnnot (SeqAnnotPtr sap)
{
  ValNodePtr list;

  list = FeatSortListFromSeqAnnot (sap);
  list = ValNodeSort (list, SortVnpByFeatSort);
  if (list != NULL) {
    sap->data = SeqFeatListFromFeatSortList (list);
    list = FeatSortListFree (list);
  }
}




NLM_EXTERN void AddFeaturesToList (DialoG d, ValNodePtr features)
{
  FeatureSelectListDlgPtr dlg;
  BaR                     vbar;
  Int4                    scroll_pos;
  ValNodePtr              tmp;

  dlg = (FeatureSelectListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || features == NULL) {
    return;
  }

  ValNodeLink (&dlg->list, features);
  tmp = FeatSortListFromFeatValNodeList (dlg->list);
  tmp = ValNodeSort (tmp, SortVnpByFeatSort);
  dlg->list = ValNodeFree (dlg->list);
  dlg->list = FeatValNodeListFromFeatSortList (tmp);
  tmp = FeatSortListFree (tmp);

  vbar = GetSlateVScrollBar ((SlatE) dlg->doc);
  scroll_pos = GetBarValue (vbar);
  PopulateFeatureSelectList (dlg, dlg->list);
  SetBarValue (vbar, scroll_pos);
}






